Compare commits
34
Commits
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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')"
|
||||
@@ -3,7 +3,7 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
|
||||
@@ -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
@@ -1 +1,15 @@
|
||||
__pycache__/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
*.so
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
.tox/
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
@@ -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)
|
||||
|
||||
+8
-1
@@ -1 +1,8 @@
|
||||
from .styles_csv_loader import *
|
||||
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 *
|
||||
|
||||
+38
-1
@@ -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"
|
||||
]
|
||||
|
||||
@@ -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())
|
||||
+31
-62
@@ -2,83 +2,56 @@ import os
|
||||
import re
|
||||
import folder_paths
|
||||
|
||||
|
||||
class StylesCSVLoader:
|
||||
"""
|
||||
Loads csv file with styles. For migration purposes from automatic1111 webui.
|
||||
Loads csv file with styles. For migration purposes from automatic11111 webui.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def load_styles_csv(styles_path: str):
|
||||
"""Loads csv file with styles. Each row has three columns:
|
||||
- style_name: string (labeled as "name" in the CSV)
|
||||
- positive_prompt: string (labeled as "prompt" in the CSV)
|
||||
- negative_prompt: string (may be empty or missing)
|
||||
"""Loads csv file with styles. It has only one column.
|
||||
Ignore the first row (header).
|
||||
positive_prompt are strings separated by comma. Each string is a prompt.
|
||||
negative_prompt are strings separated by comma. Each string is a prompt.
|
||||
|
||||
Returns:
|
||||
dict: Dictionary of styles, where each key is style_name and value is [positive_prompt, negative_prompt]
|
||||
list: List of styles. Each style is a dict with keys: style_name and value: [positive_prompt, negative_prompt]
|
||||
"""
|
||||
error_styles = {
|
||||
"Error loading styles.csv, check the console": ["", ""]}
|
||||
|
||||
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}
|
||||
""")
|
||||
return error_styles
|
||||
|
||||
try:
|
||||
styles = {}
|
||||
with open(styles_path, "r", encoding="utf-8") as f:
|
||||
reader = csv.reader(f)
|
||||
next(reader) # Skip header row
|
||||
|
||||
# Start counting from 2 (after header)
|
||||
for i, row in enumerate(reader, start=2):
|
||||
try:
|
||||
# Initialize variables to avoid "used before assigned" errors
|
||||
style_name = ""
|
||||
positive_prompt = ""
|
||||
negative_prompt = ""
|
||||
|
||||
# Handle rows with fewer than 3 columns
|
||||
if len(row) == 0:
|
||||
# Skip empty rows
|
||||
continue
|
||||
elif len(row) == 1:
|
||||
print(
|
||||
f"Error parsing line {i}: {','.join(row)} -> Expected at least 2 columns, got {len(row)}")
|
||||
continue
|
||||
elif len(row) == 2:
|
||||
# Missing negative prompt - treat as empty string
|
||||
style_name, positive_prompt = row
|
||||
# negative_prompt already initialized as empty string
|
||||
elif len(row) >= 3:
|
||||
# Normal case or extra columns (ignore extras)
|
||||
style_name, positive_prompt, negative_prompt = row[0], row[1], row[2]
|
||||
|
||||
styles[style_name] = [positive_prompt, negative_prompt]
|
||||
except Exception as e:
|
||||
# Use a safe version of the row for error reporting
|
||||
row_str = ','.join(row) if isinstance(
|
||||
row, list) else str(row)
|
||||
print(f"Error parsing line {i}: {row_str} -> {e}")
|
||||
|
||||
Your current root directory is: {normalized_base_path}
|
||||
""")
|
||||
return styles
|
||||
|
||||
try:
|
||||
with open(styles_path, "r", encoding="utf-8") as f:
|
||||
styles = [[x.replace('"', '').replace('\n', '') for x in re.split(
|
||||
',(?=(?:[^"]*"[^"]*")*[^"]*$)', 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}
|
||||
Error: {e}
|
||||
""")
|
||||
return error_styles
|
||||
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):
|
||||
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()),),
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING")
|
||||
@@ -87,16 +60,12 @@ Error: {e}
|
||||
CATEGORY = "loaders"
|
||||
|
||||
def execute(self, styles):
|
||||
return (
|
||||
self.styles_csv[styles][0],
|
||||
self.styles_csv[styles][1]
|
||||
)
|
||||
return (self.styles_csv[styles][0], self.styles_csv[styles][1])
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"Load Styles CSV": StylesCSVLoader
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"StylesCSVLoader": "Load Styles CSV Node"
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# Test package for ComfyUI-Styles_CSV_Loader
|
||||
Vendored
+5
@@ -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"
|
||||
|
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
name,prompt
|
||||
"Missing Column","this CSV only has 2 columns instead of 3"
|
||||
"Also Missing","another incomplete row"
|
||||
|
Vendored
+6
@@ -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"
|
||||
|
@@ -0,0 +1,158 @@
|
||||
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)
|
||||
# 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.
|
||||
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()
|
||||
Reference in New Issue
Block a user