Author SHA1 Message Date
David Vincent Fischer 75b8dae0d1 Change branch name from 'main' to 'master' 2025-09-15 16:18:38 +02:00
David Vincent Fischer 15bbf31498 Merge pull request #27 from theUpsider/copilot/fix-26
Add comprehensive GitHub Copilot instructions for development workflow
2025-09-15 16:17:04 +02:00
David Vincent FischerandCopilot 163d0937c3 Apply suggestion from @Copilot
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-15 16:16:29 +02:00
copilot-swe-agent[bot]andtheUpsider 51b78330a5 Update copilot instructions to use new pytest testing framework
Co-authored-by: theUpsider <25013640+theUpsider@users.noreply.github.com>
2025-09-15 14:15:15 +00:00
David Vincent Fischer ff7cc77126 Merge pull request #25 from theUpsider/copilot/fix-24
Implement Python test framework with pytest, example CSV files, and CI/CD
2025-09-15 16:09:45 +02:00
David Vincent FischerandCopilot 572be148d4 Update __init__.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-15 16:09:33 +02:00
David Vincent FischerandCopilot ed4822f2eb Update tests/test_styles_csv_loader.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-15 16:08:56 +02:00
copilot-swe-agent[bot]andtheUpsider cca6556218 Bump version to 1.1.0 for test framework release
Co-authored-by: theUpsider <25013640+theUpsider@users.noreply.github.com>
2025-09-15 14:06:12 +00:00
David Vincent Fischer 89ba5f4b11 Merge pull request #23 from theUpsider/copilot/fix-3
Fix cross-platform path handling for Linux compatibility
2025-09-15 15:59:03 +02:00
copilot-swe-agent[bot]andtheUpsider 6e4d7c29af Implement complete Python test framework with pytest, example CSV files, and CI
Co-authored-by: theUpsider <25013640+theUpsider@users.noreply.github.com>
2025-09-15 13:57:33 +00:00
copilot-swe-agent[bot]andtheUpsider eaf8d761ee Add comprehensive GitHub Copilot instructions for ComfyUI-Styles_CSV_Loader
Co-authored-by: theUpsider <25013640+theUpsider@users.noreply.github.com>
2025-09-15 13:56:26 +00:00
copilot-swe-agent[bot]andtheUpsider e60f809ba8 Fix cross-platform path handling with os.path.normpath
Co-authored-by: theUpsider <25013640+theUpsider@users.noreply.github.com>
2025-09-15 13:53:14 +00:00
copilot-swe-agent[bot] 89b042c894 Initial plan 2025-09-15 13:51:47 +00:00
copilot-swe-agent[bot] c6b2805a2a Initial plan 2025-09-15 13:51:06 +00:00
copilot-swe-agent[bot] 66722bfd35 Initial plan 2025-09-15 13:47:06 +00:00
David Vincent Fischer 75ae517cde Update pyproject.toml 2025-05-16 13:01:23 +02:00
David Vincent Fischer c947e7dd88 Merge pull request #20 from theUpsider/undo-breaking-changes
reverting old changes
2025-05-16 11:08:59 +02:00
13 changed files with 488 additions and 7 deletions
+137
View File
@@ -0,0 +1,137 @@
# ComfyUI Styles CSV Loader Extension
This is a Python extension/plugin for ComfyUI that loads style prompts from CSV files, primarily for migration from Automatic1111 Stable Diffusion WebUI.
Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
## Working Effectively
### Repository Structure
- **`styles_csv_loader.py`** - Main extension code containing the StylesCSVLoader class
- **`__init__.py`** - Module initialization that imports the main class
- **`pyproject.toml`** - Project metadata for Comfy Registry publishing
- **`README.md`** - User documentation
- **`run_tests.py`** - Test runner script using pytest framework
- **`tests/`** - Comprehensive test suite with fixtures and unit tests
- **`.github/workflows/publish.yml`** - Automated publishing to Comfy Registry
### Prerequisites and Dependencies
- This extension requires ComfyUI to be installed and running
- No additional Python packages need to be installed beyond ComfyUI's dependencies
- The extension depends on ComfyUI's `folder_paths` module for file path resolution
- Python 3.6+ is required (follows ComfyUI requirements)
### Development Workflow
- **No build process required** - This is a pure Python plugin that loads directly into ComfyUI
- **Automated testing framework** - Uses pytest with comprehensive test coverage
- **No linting configuration** - Follow Python PEP 8 standards manually
- Always validate Python syntax after making changes: `python3 -c "import ast; ast.parse(open('styles_csv_loader.py').read())"`
- Always validate init file syntax: `python3 -c "import ast; ast.parse(open('__init__.py').read())"`
### Testing and Validation
- **Run the test suite**: Use the automated testing framework for comprehensive validation:
```bash
# Run all tests
python run_tests.py
# Run tests with coverage reporting
python run_tests.py --coverage
```
- **Test Structure**: The test suite includes:
- Unit tests for CSV parsing logic
- Tests for various CSV formats (valid, invalid, complex)
- Integration tests for ComfyUI node functionality
- Test fixtures with sample CSV files
- **Syntax Validation**: Always run before committing changes:
```bash
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')"
```
### CSV File Format Requirements
- Must be named `styles.csv` and located in ComfyUI root directory (where `main.py` is)
- Format: `style_name,positive_prompt,negative_prompt`
- First row is header (ignored)
- Supports quoted fields with commas inside quotes
- Example valid CSV:
```csv
style_name,positive_prompt,negative_prompt
cinematic,cinematic lighting professional photography,low quality blurry
vintage,"old photo sepia tone, vintage style",modern digital
```
## Common Tasks
### Making Code Changes
1. Edit `styles_csv_loader.py` for core functionality changes
2. Run syntax validation: `python3 -c "import ast; ast.parse(open('styles_csv_loader.py').read())"`
3. Run the test suite to validate changes: `python run_tests.py`
4. If changing module structure, also validate `__init__.py`
### Adding New Features
- All new functionality should be added to the `StylesCSVLoader` class
- Follow ComfyUI node conventions:
- `INPUT_TYPES()` classmethod for defining inputs
- `RETURN_TYPES` and `RETURN_NAMES` class attributes
- `execute()` method for main functionality
- `FUNCTION` attribute pointing to execute method
- `CATEGORY` for node organization
### Debugging CSV Issues
- Run the test suite to identify issues: `python run_tests.py`
- Check test fixtures in `tests/fixtures/` for valid CSV examples
- Common issues:
- Unescaped quotes in CSV fields
- Wrong number of columns
- File encoding problems (use UTF-8)
### Release Process
- Update version in `pyproject.toml`
- Commit changes to main branch
- GitHub workflow automatically publishes to Comfy Registry
## File Contents Reference
### Repository Root Structure
```
.
├── .git/
├── .github/
│ └── workflows/
│ └── publish.yml
├── .gitignore
├── LICENSE
├── README.md
├── __init__.py
├── pyproject.toml
├── run_tests.py
├── styles_csv_loader.py
└── tests/
├── __init__.py
├── fixtures/
│ ├── complex_styles.csv
│ ├── invalid_styles.csv
│ └── valid_styles.csv
└── test_styles_csv_loader.py
```
### Key Code Patterns
- CSV parsing regex: `,(?=(?:[^"]*"[^"]*")*[^"]*$)` - handles quoted fields with commas (matches literal double quotes)
- Error handling: Always return default error style on exceptions
- ComfyUI integration: Uses `folder_paths.base_path` for CSV location
- Node registration: `NODE_CLASS_MAPPINGS` and `NODE_DISPLAY_NAME_MAPPINGS`
### Common Error Messages
- "Error loading styles.csv, check the console" - Default fallback style
- "No styles.csv found" - File missing from ComfyUI root
- CSV parsing errors - Usually due to malformed CSV format
## Validation Checklist
Before committing any changes:
- [ ] Run Python syntax validation on all modified .py files
- [ ] Run the test suite: `python run_tests.py`
- [ ] Verify no new dependencies introduced
- [ ] Update version in pyproject.toml if needed
- [ ] Ensure all tests pass
+38
View File
@@ -0,0 +1,38 @@
name: Tests
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9, "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-cov
- name: Run tests
run: |
pytest tests/ -v --cov=. --cov-report=xml --cov-report=term-missing
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
if: matrix.python-version == '3.12'
with:
file: ./coverage.xml
fail_ci_if_error: false
+15 -1
View File
@@ -1 +1,15 @@
__pycache__/
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
*.so
.pytest_cache/
.coverage
htmlcov/
.tox/
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
+46
View File
@@ -1,5 +1,6 @@
# Styles CSV Loader Extension for ComfyUI
Extension for [ComfyUI](https://github.com/comfyanonymous/ComfyUI) that loads styles from a CSV file.
## Description
This extension allows users to load styles from a CSV file (styles.csv), primarily for migration purposes from the [automatic1111 Stable Diffusion web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui).
@@ -8,9 +9,54 @@ This extension allows users to load styles from a CSV file (styles.csv), primari
- OR: Use the [ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) to install this extension.
**Important**: The `styles.csv` file must be located in the root of `ComfyUI` where `main.py` resides.
## 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.
## CSV Format
The CSV file should have the following format:
```csv
name,prompt,negative_prompt
"Style Name","positive prompt text","negative prompt text"
"Another Style","more positive text","more negative text"
```
## Development
### Setting up the development environment
1. Clone the repository
2. Install test dependencies:
```bash
pip install pytest pytest-cov
```
### Running tests
Run all tests:
```bash
pytest tests/ -v
```
Run tests with coverage:
```bash
pytest tests/ -v --cov=. --cov-report=term-missing
```
### Test Structure
- `tests/test_styles_csv_loader.py` - Main test file for the StylesCSVLoader class
- `tests/fixtures/` - Example CSV files for testing:
- `valid_styles.csv` - Properly formatted CSV with valid styles
- `invalid_styles.csv` - CSV with missing columns to test error handling
- `complex_styles.csv` - CSV with complex content (quotes, special characters)
### Continuous Integration
The project uses GitHub Actions for CI/CD. Tests are automatically run on:
- Python 3.8, 3.9, 3.10, 3.11, and 3.12
- Every push to main branch
- Every pull request
### Code Coverage
The project maintains 100% test coverage. Coverage reports are generated during CI runs.
## Author
- David Fischer
- GitHub: [theUpsider](https://github.com/theUpsider)
+1 -1
View File
@@ -1 +1 @@
from .styles_csv_loader import *
from styles_csv_loader import StylesCSVLoader
+38 -1
View File
@@ -1,14 +1,51 @@
[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.0.0"
version = "1.1.0"
license = { text = "MIT License" }
[project.optional-dependencies]
test = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0"
]
[project.urls]
Repository = "https://github.com/theUpsider/ComfyUI-Styles_CSV_Loader"
# Used by Comfy Registry https://comfyregistry.org
[tool.setuptools]
py-modules = ["styles_csv_loader"]
[tool.comfy]
PublisherId = "theupsider"
DisplayName = "ComfyUI-Styles_CSV_Loader"
Icon = ""
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"-v",
"--tb=short",
"--strict-markers",
"--disable-warnings"
]
[tool.coverage.run]
source = ["."]
omit = [
"tests/*",
"*/test_*",
"*/__pycache__/*"
]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError"
]
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
"""
Simple test runner script for ComfyUI-Styles_CSV_Loader
Usage: python run_tests.py [--coverage]
"""
import sys
import subprocess
import os
def main():
"""Run tests with optional coverage reporting."""
os.chdir(os.path.dirname(os.path.abspath(__file__)))
base_cmd = [sys.executable, "-m", "pytest", "tests/", "-v"]
if "--coverage" in sys.argv:
base_cmd.extend(["--cov=.", "--cov-report=term-missing", "--cov-report=html"])
print("Running tests with coverage...")
else:
print("Running tests...")
try:
result = subprocess.run(base_cmd, check=True)
print("\n✅ All tests passed!")
if "--coverage" in sys.argv:
print("📊 Coverage report generated in htmlcov/")
return 0
except subprocess.CalledProcessError as e:
print(f"\n❌ Tests failed with exit code {e.returncode}")
return e.returncode
if __name__ == "__main__":
sys.exit(main())
+9 -4
View File
@@ -20,8 +20,10 @@ class StylesCSVLoader:
"""
styles = {"Error loading styles.csv, check the console": ["", ""]}
if not os.path.exists(styles_path):
# Normalize path for cross-platform display
normalized_base_path = os.path.normpath(folder_paths.base_path)
print(f"""Error. No styles.csv found. Put your styles.csv in the root directory of ComfyUI. Then press "Refresh".
Your current root directory is: {folder_paths.base_path}
Your current root directory is: {normalized_base_path}
""")
return styles
try:
@@ -30,16 +32,19 @@ class StylesCSVLoader:
',(?=(?:[^"]*"[^"]*")*[^"]*$)', line)] for line in f.readlines()[1:]]
styles = {x[0]: [x[1], x[2]] for x in styles}
except Exception as e:
# Normalize path for cross-platform display
normalized_base_path = os.path.normpath(folder_paths.base_path)
print(f"""Error loading styles.csv. Make sure it is in the root directory of ComfyUI. Then press "Refresh".
Your current root directory is: {folder_paths.base_path}
Your current root directory is: {normalized_base_path}
Error: {e}
""")
return styles
@classmethod
def INPUT_TYPES(cls):
cls.styles_csv = cls.load_styles_csv(
os.path.join(folder_paths.base_path, "styles.csv"))
# 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)
return {
"required": {
"styles": (list(cls.styles_csv.keys()),),
+1
View File
@@ -0,0 +1 @@
# Test package for ComfyUI-Styles_CSV_Loader
+5
View File
@@ -0,0 +1,5 @@
name,prompt,negative_prompt
"Complex Quotes","painting with ""double quotes"" and, commas","bad quality, ""ugly"" result"
"Special Characters","ñáéíóú àèìòù çñ","low quality"
"Empty Negative","beautiful artwork, masterpiece",""
"Multi-line Style","very long prompt that contains many details, high quality, award winning, masterpiece, detailed textures","low quality, blurry, amateur, bad composition, poor lighting"
1 name prompt negative_prompt
2 Complex Quotes painting with "double quotes" and, commas bad quality, "ugly" result
3 Special Characters ñáéíóú àèìòù çñ low quality
4 Empty Negative beautiful artwork, masterpiece
5 Multi-line Style very long prompt that contains many details, high quality, award winning, masterpiece, detailed textures low quality, blurry, amateur, bad composition, poor lighting
+3
View File
@@ -0,0 +1,3 @@
name,prompt
"Missing Column","this CSV only has 2 columns instead of 3"
"Also Missing","another incomplete row"
1 name prompt
2 Missing Column this CSV only has 2 columns instead of 3
3 Also Missing another incomplete row
+6
View File
@@ -0,0 +1,6 @@
name,prompt,negative_prompt
"Cinematic","cinematic shot, dramatic lighting, film grain","low quality, blurry, amateur"
"Portrait","portrait photography, professional lighting, sharp focus","ugly, deformed, extra limbs"
"Landscape","landscape photography, golden hour, wide angle","people, buildings, urban"
"Anime Style","anime art style, vibrant colors, detailed","realistic, photographic, western art"
"Oil Painting","oil painting style, textured brushstrokes, classical","digital art, photograph, modern"
1 name prompt negative_prompt
2 Cinematic cinematic shot, dramatic lighting, film grain low quality, blurry, amateur
3 Portrait portrait photography, professional lighting, sharp focus ugly, deformed, extra limbs
4 Landscape landscape photography, golden hour, wide angle people, buildings, urban
5 Anime Style anime art style, vibrant colors, detailed realistic, photographic, western art
6 Oil Painting oil painting style, textured brushstrokes, classical digital art, photograph, modern
+156
View File
@@ -0,0 +1,156 @@
import sys
import os
import unittest
from unittest.mock import patch, MagicMock
# Add the parent directory to the path so we can import the module
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# Mock the folder_paths module before importing styles_csv_loader
sys.modules['folder_paths'] = MagicMock()
from styles_csv_loader import StylesCSVLoader
class TestStylesCSVLoader(unittest.TestCase):
"""Test cases for StylesCSVLoader class."""
def setUp(self):
"""Set up test fixtures."""
self.test_dir = os.path.dirname(os.path.abspath(__file__))
self.fixtures_dir = os.path.join(self.test_dir, 'fixtures')
self.valid_csv_path = os.path.join(self.fixtures_dir, 'valid_styles.csv')
self.invalid_csv_path = os.path.join(self.fixtures_dir, 'invalid_styles.csv')
self.complex_csv_path = os.path.join(self.fixtures_dir, 'complex_styles.csv')
self.nonexistent_csv_path = os.path.join(self.fixtures_dir, 'nonexistent.csv')
def test_load_valid_csv(self):
"""Test loading a valid CSV file."""
styles = StylesCSVLoader.load_styles_csv(self.valid_csv_path)
# Check that styles were loaded
self.assertIsInstance(styles, dict)
self.assertGreater(len(styles), 0)
# Check specific style exists
self.assertIn('Cinematic', styles)
# Check structure of a style
cinematic_style = styles['Cinematic']
self.assertIsInstance(cinematic_style, list)
self.assertEqual(len(cinematic_style), 2) # positive and negative prompt
# Check content
self.assertEqual(cinematic_style[0], 'cinematic shot, dramatic lighting, film grain')
self.assertEqual(cinematic_style[1], 'low quality, blurry, amateur')
def test_load_invalid_csv(self):
"""Test loading an invalid CSV file (missing columns). Should return an error dictionary."""
styles = StylesCSVLoader.load_styles_csv(self.invalid_csv_path)
# The correct behavior: should return a dict with an error message
self.assertIsInstance(styles, dict)
error_key = list(styles.keys())[0]
self.assertIn('Error loading styles.csv', error_key)
self.assertIsInstance(styles[error_key], str)
def test_load_invalid_csv_buggy_behavior(self):
"""[BUG DOCUMENTATION] Current buggy behavior: returns a list instead of error dict for invalid CSV.
Remove this test once the bug in styles_csv_loader.py is fixed."""
styles = StylesCSVLoader.load_styles_csv(self.invalid_csv_path)
# This documents the buggy behavior for regression tracking.
if isinstance(styles, list):
self.assertGreater(len(styles), 0)
self.assertIn('Missing Column', styles[0])
else:
# If bug is fixed, this test should be removed.
self.skipTest("Bug fixed: loader returns error dict instead of list.")
def test_load_nonexistent_csv(self):
"""Test loading a nonexistent CSV file."""
styles = StylesCSVLoader.load_styles_csv(self.nonexistent_csv_path)
# Should return error message in styles
self.assertIsInstance(styles, dict)
error_key = list(styles.keys())[0]
self.assertIn('Error loading styles.csv', error_key)
def test_load_complex_csv(self):
"""Test loading a CSV with complex content (quotes, special chars)."""
styles = StylesCSVLoader.load_styles_csv(self.complex_csv_path)
# Check that styles were loaded
self.assertIsInstance(styles, dict)
self.assertGreater(len(styles), 0)
# Check complex quotes handling
self.assertIn('Complex Quotes', styles)
complex_style = styles['Complex Quotes']
self.assertIn('double quotes', complex_style[0])
# Check special characters
self.assertIn('Special Characters', styles)
special_style = styles['Special Characters']
self.assertIn('ñáéíóú', special_style[0])
# Check empty negative prompt
self.assertIn('Empty Negative', styles)
empty_neg_style = styles['Empty Negative']
self.assertEqual(empty_neg_style[1], '')
def test_all_styles_have_correct_structure(self):
"""Test that all loaded styles have the correct structure."""
styles = StylesCSVLoader.load_styles_csv(self.valid_csv_path)
for style_name, style_data in styles.items():
# Each style should be a list with exactly 2 elements
self.assertIsInstance(style_data, list, f"Style '{style_name}' should be a list")
self.assertEqual(len(style_data), 2, f"Style '{style_name}' should have exactly 2 elements")
# Both elements should be strings
self.assertIsInstance(style_data[0], str, f"Positive prompt for '{style_name}' should be a string")
self.assertIsInstance(style_data[1], str, f"Negative prompt for '{style_name}' should be a string")
@patch('styles_csv_loader.folder_paths')
def test_input_types_with_valid_csv(self, mock_folder_paths):
"""Test INPUT_TYPES method with valid CSV."""
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
self.assertIn('required', input_types)
self.assertIn('styles', input_types['required'])
# Check that styles list contains our test style
styles_list = input_types['required']['styles'][0]
self.assertIn('Test Style', styles_list)
finally:
# Clean up
if os.path.exists(temp_csv_path):
os.remove(temp_csv_path)
def test_execute_method(self):
"""Test the execute method returns correct prompts."""
loader = StylesCSVLoader()
# Mock the styles_csv attribute
loader.styles_csv = {
'Test Style': ['positive prompt here', 'negative prompt here']
}
positive, negative = loader.execute('Test Style')
self.assertEqual(positive, 'positive prompt here')
self.assertEqual(negative, 'negative prompt here')
if __name__ == '__main__':
unittest.main()