Author SHA1 Message Date
David Vincent Fischer 255a4af7fc Bump version from 2.0.0 to 3.0.0
chore: bumped version
2025-09-15 17:48:28 +02:00
David Vincent Fischer a253201065 Merge pull request #37 from theUpsider/copilot/fix-13
Add support for custom CSV file paths with fallback mechanism
2025-09-15 17:46:57 +02:00
copilot-swe-agent[bot]andtheUpsider e3eb3a2519 Add support for custom CSV file paths with fallback mechanism
Co-authored-by: theUpsider <25013640+theUpsider@users.noreply.github.com>
2025-09-15 15:38:00 +00:00
copilot-swe-agent[bot] d0179b3316 Initial plan 2025-09-15 15:32:58 +00:00
David Vincent Fischer 6ffb6ee696 Update pyproject.toml
chore: bumped up version
2025-09-15 17:26:52 +02:00
David Vincent Fischer 5c32e9463c Add badges for publishing and testing workflows
added status badge
2025-09-15 17:25:35 +02:00
David Vincent Fischer 4e917d492e Merge pull request #36 from theUpsider/paths
Paths
2025-09-15 17:23:55 +02:00
David Fischer 84993fad28 Fix import statement for styles_csv_loader in __init__.py 2025-09-15 17:23:17 +02:00
David Fischer 292be57ab1 Reorder import statement for styles_csv_loader in __init__.py 2025-09-15 17:17:53 +02:00
David Fischer d479c5a667 Refactor import statements and update sys.path handling in __init__.py 2025-09-15 17:17:36 +02:00
David Vincent Fischer 5e4e471aaf Merge pull request #32 from theUpsider/copilot/fix-31
Add Copilot setup workflow for Python-based ComfyUI extension
2025-09-15 16:44:04 +02:00
copilot-swe-agent[bot]andtheUpsider 4454dc6740 Add Copilot setup workflow for Python-based ComfyUI extension
Co-authored-by: theUpsider <25013640+theUpsider@users.noreply.github.com>
2025-09-15 14:36:50 +00:00
David Vincent Fischer 5b5787b37c Merge pull request #30 from theUpsider/copilot/fix-29
Fix CSV loader to always return dict instead of list on invalid input
2025-09-15 16:35:29 +02:00
David Vincent Fischer 183f62e6e4 Merge pull request #33 from theUpsider/theUpsider-patch-2
Update issue templates
2025-09-15 16:34:20 +02:00
David Vincent Fischer a99ae9bb6d Update issue templates 2025-09-15 16:34:11 +02:00
copilot-swe-agent[bot] 5fbb3870c0 Initial plan 2025-09-15 14:32:25 +00:00
copilot-swe-agent[bot]andtheUpsider 71edaa7cc5 Fix CSV loader to always return dict, update test expectations
Co-authored-by: theUpsider <25013640+theUpsider@users.noreply.github.com>
2025-09-15 14:29:18 +00:00
copilot-swe-agent[bot] aa89d00744 Initial plan 2025-09-15 14:25:46 +00:00
David Vincent Fischer 17b7f91e8f Delete issues/1.md 2025-09-15 16:24:25 +02:00
David Vincent Fischer ec639b22f2 Create issue for failing test in loader 2025-09-15 16:24:01 +02:00
David Vincent Fischer 69a466ec65 Change default branch from 'main' to 'master' 2025-09-15 16:20:46 +02:00
David Vincent Fischer f04c04d786 Merge pull request #28 from theUpsider/theUpsider-patch-1
Change branch name from 'main' to 'master'
2025-09-15 16:19:55 +02:00
9 changed files with 246 additions and 9 deletions
+38
View File
@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: "[Bug] ..."
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[Feature] ..."
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+44
View File
@@ -0,0 +1,44 @@
name: "Copilot Setup Steps"
# Automatically run the setup steps when they are changed to allow for easy validation, and
# allow manual testing through the repository's "Actions" tab
"on":
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml
jobs:
# The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
copilot-setup-steps:
runs-on: ubuntu-latest
# Set the permissions to the lowest permissions possible needed for your steps.
# Copilot will be given its own token for its operations.
permissions:
# If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete.
contents: read
# You can define any steps you want, and they will run before the agent starts.
# If you do not check out your code, Copilot will do this for you.
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.8"
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-cov
- name: Validate Python syntax
run: |
python3 -c "import ast; ast.parse(open('styles_csv_loader.py').read()); print('styles_csv_loader.py syntax valid')"
python3 -c "import ast; ast.parse(open('__init__.py').read()); print('__init__.py syntax valid')"
+1 -1
View File
@@ -3,7 +3,7 @@ on:
workflow_dispatch:
push:
branches:
- main
- master
paths:
- "pyproject.toml"
+29 -1
View File
@@ -1,3 +1,5 @@
[![Publish to Comfy registry](https://github.com/theUpsider/ComfyUI-Styles_CSV_Loader/actions/workflows/publish.yml/badge.svg)](https://github.com/theUpsider/ComfyUI-Styles_CSV_Loader/actions/workflows/publish.yml)
[![Tests](https://github.com/theUpsider/ComfyUI-Styles_CSV_Loader/actions/workflows/test.yml/badge.svg)](https://github.com/theUpsider/ComfyUI-Styles_CSV_Loader/actions/workflows/test.yml)
# Styles CSV Loader Extension for ComfyUI
Extension for [ComfyUI](https://github.com/comfyanonymous/ComfyUI) that loads styles from a CSV file.
@@ -13,6 +15,15 @@ This extension allows users to load styles from a CSV file (styles.csv), primari
## Nodes Description
Each style is represented as a dictionary with the keys being `style_name` and the values being a list containing `positive_prompt` and `negative_prompt`. The prompts are outputs of this Node.
### Custom CSV Files
The extension now supports loading styles from custom CSV files in addition to the default `styles.csv`. You can specify a custom CSV file path using the optional `csv_file_path` parameter:
- **Relative paths**: Relative to the ComfyUI root directory (e.g., `"my_styles/portrait_styles.csv"`)
- **Absolute paths**: Full file system paths (e.g., `"/path/to/my/custom_styles.csv"`)
- **Default behavior**: When no custom path is specified, it uses `styles.csv` from the ComfyUI root directory
If a selected style is not found in the custom CSV file, the node will fall back to the default styles loaded from `styles.csv`.
## CSV Format
The CSV file should have the following format:
```csv
@@ -21,6 +32,23 @@ name,prompt,negative_prompt
"Another Style","more positive text","more negative text"
```
### Examples
You can organize your styles in multiple CSV files for different purposes:
**portraits.csv**:
```csv
name,prompt,negative_prompt
"Professional Portrait","portrait photography, professional lighting, sharp focus","ugly, deformed, extra limbs"
"Vintage Portrait","vintage portrait, sepia tone, classical","modern, digital, colorful"
```
**landscapes.csv**:
```csv
name,prompt,negative_prompt
"Golden Hour","landscape photography, golden hour, wide angle","people, buildings, urban"
"Mountain Vista","mountain landscape, dramatic clouds, epic view","flat, boring, low quality"
```
## Development
### Setting up the development environment
@@ -60,4 +88,4 @@ The project maintains 100% test coverage. Coverage reports are generated during
## Author
- David Fischer
- GitHub: [theUpsider](https://github.com/theUpsider)
- Support me on [BuyMeACoffee](https://www.buymeacoffee.com/theupsider)
- Support me on [BuyMeACoffee](https://www.buymeacoffee.com/theupsider)
+8 -1
View File
@@ -1 +1,8 @@
from styles_csv_loader import StylesCSVLoader
import sys
import os
# Add current directory to sys.path to ensure we can import styles_csv_loader
current_dir = os.path.dirname(os.path.abspath(__file__))
if current_dir not in sys.path:
sys.path.insert(0, current_dir)
from styles_csv_loader import *
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-styles_csv_loader"
description = "This extension allows users to load styles from a CSV file, primarily for migration purposes from the automatic1111 Stable Diffusion web UI."
version = "1.1.0"
version = "3.0.0"
license = { text = "MIT License" }
[project.optional-dependencies]
+26 -4
View File
@@ -38,18 +38,22 @@ class StylesCSVLoader:
Your current root directory is: {normalized_base_path}
Error: {e}
""")
# Return the error dictionary instead of the intermediate styles list
styles = {"Error loading styles.csv, check the console": ["", ""]}
return styles
@classmethod
def INPUT_TYPES(cls):
# Use os.path.normpath to ensure cross-platform compatibility
styles_path = os.path.normpath(os.path.join(folder_paths.base_path, "styles.csv"))
cls.styles_csv = cls.load_styles_csv(styles_path)
default_styles_path = os.path.normpath(os.path.join(folder_paths.base_path, "styles.csv"))
cls.styles_csv = cls.load_styles_csv(default_styles_path)
return {
"required": {
"styles": (list(cls.styles_csv.keys()),),
},
"optional": {
"csv_file_path": ("STRING", {"default": "styles.csv", "multiline": False}),
}
}
RETURN_TYPES = ("STRING", "STRING")
@@ -57,7 +61,25 @@ class StylesCSVLoader:
FUNCTION = "execute"
CATEGORY = "loaders"
def execute(self, styles):
def execute(self, styles, csv_file_path="styles.csv"):
# Handle custom CSV file path
if csv_file_path != "styles.csv":
# Check if path is absolute or relative
if os.path.isabs(csv_file_path):
custom_styles_path = os.path.normpath(csv_file_path)
else:
# Relative path from ComfyUI root directory
custom_styles_path = os.path.normpath(os.path.join(folder_paths.base_path, csv_file_path))
# Load styles from custom path
custom_styles = self.load_styles_csv(custom_styles_path)
if styles in custom_styles:
return (custom_styles[styles][0], custom_styles[styles][1])
else:
# Fall back to default styles if style not found in custom file
pass
# Use default styles
return (self.styles_csv[styles][0], self.styles_csv[styles][1])
+79 -1
View File
@@ -52,7 +52,9 @@ class TestStylesCSVLoader(unittest.TestCase):
self.assertIsInstance(styles, dict)
error_key = list(styles.keys())[0]
self.assertIn('Error loading styles.csv', error_key)
self.assertIsInstance(styles[error_key], str)
# Style values should always be lists with positive and negative prompts
self.assertIsInstance(styles[error_key], list)
self.assertEqual(len(styles[error_key]), 2)
def test_load_invalid_csv_buggy_behavior(self):
"""[BUG DOCUMENTATION] Current buggy behavior: returns a list instead of error dict for invalid CSV.
@@ -150,6 +152,82 @@ class TestStylesCSVLoader(unittest.TestCase):
self.assertEqual(positive, 'positive prompt here')
self.assertEqual(negative, 'negative prompt here')
def test_execute_method_with_custom_csv_path(self):
"""Test the execute method with custom CSV file path."""
loader = StylesCSVLoader()
# Mock the default styles_csv attribute
loader.styles_csv = {
'Default Style': ['default positive', 'default negative']
}
# Test with custom CSV path
positive, negative = loader.execute('Cinematic', self.valid_csv_path)
self.assertEqual(positive, 'cinematic shot, dramatic lighting, film grain')
self.assertEqual(negative, 'low quality, blurry, amateur')
def test_execute_method_with_custom_csv_fallback(self):
"""Test that execute method falls back to default when style not found in custom CSV."""
loader = StylesCSVLoader()
# Mock the default styles_csv attribute
loader.styles_csv = {
'Default Style': ['default positive', 'default negative']
}
# Test with custom CSV path but style that doesn't exist in custom file
positive, negative = loader.execute('Default Style', self.valid_csv_path)
self.assertEqual(positive, 'default positive')
self.assertEqual(negative, 'default negative')
def test_execute_method_with_absolute_path(self):
"""Test execute method with absolute path to custom CSV."""
loader = StylesCSVLoader()
# Mock the default styles_csv attribute
loader.styles_csv = {
'Default Style': ['default positive', 'default negative']
}
# Test with absolute path
absolute_path = os.path.abspath(self.valid_csv_path)
positive, negative = loader.execute('Cinematic', absolute_path)
self.assertEqual(positive, 'cinematic shot, dramatic lighting, film grain')
self.assertEqual(negative, 'low quality, blurry, amateur')
@patch('styles_csv_loader.folder_paths')
def test_input_types_includes_csv_file_path(self, mock_folder_paths):
"""Test that INPUT_TYPES includes the csv_file_path optional parameter."""
mock_folder_paths.base_path = self.fixtures_dir
# Create a temporary styles.csv in the fixtures directory
temp_csv_path = os.path.join(self.fixtures_dir, 'styles.csv')
with open(temp_csv_path, 'w', encoding='utf-8') as f:
f.write('name,prompt,negative_prompt\n')
f.write('"Test Style","test prompt","test negative"\n')
try:
input_types = StylesCSVLoader.INPUT_TYPES()
# Check structure includes optional csv_file_path
self.assertIn('required', input_types)
self.assertIn('optional', input_types)
self.assertIn('csv_file_path', input_types['optional'])
# Check csv_file_path is a STRING type with default
csv_param = input_types['optional']['csv_file_path']
self.assertEqual(csv_param[0], 'STRING')
self.assertIn('default', csv_param[1])
self.assertEqual(csv_param[1]['default'], 'styles.csv')
finally:
# Clean up
if os.path.exists(temp_csv_path):
os.remove(temp_csv_path)
if __name__ == '__main__':