Compare commits
2
Commits
5c7d300619
...
9047d11123
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9047d11123 | ||
|
|
5e8f1b9774 |
@@ -0,0 +1,22 @@
|
||||
# Agent Instructions: PhotoTools3
|
||||
|
||||
## Environment Setup
|
||||
Requires a `.env` file with the following variables:
|
||||
- `llm_api_key`: API key for the LLM provider.
|
||||
- `llm_api_base_url`: Base URL for the LLM API.
|
||||
- `llm_default_temperature`: Float value for temperature.
|
||||
|
||||
## Development Toolchain
|
||||
- **Dependency Management**: Use `uv`.
|
||||
- **Python Version**: Requires Python `>=3.14`.
|
||||
- **Testing**: Run `pytest` to execute the test suite in `test/`.
|
||||
- **Linting & Formatting**: Use `ruff` and `black`.
|
||||
|
||||
## CLI Usage
|
||||
The primary entry point is the `photools3` command:
|
||||
- `photools3 keywords <image_path>`: Extracts keywords and captions from a single image.
|
||||
|
||||
## Architecture & Logic
|
||||
- **`src/photools3/photo.py`**: Core image processing logic, including resizing, RGB conversion, and generating base64 JPEG encodings specifically for LLM vision compatibility.
|
||||
- **`src/photools3/llm_session.py`**: Manages the workflow of sending processed images to the LLM and parsing results.
|
||||
- **Configuration**: The list of keywords to extract is maintained in `config/keywords.txt`.
|
||||
@@ -0,0 +1,80 @@
|
||||
import pytest
|
||||
from typer.testing import CliRunner
|
||||
from unittest.mock import patch, MagicMock
|
||||
from pathlib import Path
|
||||
from photools3.cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
@pytest.fixture
|
||||
def mock_photo_logic(tmp_path):
|
||||
"""Fixture to mock the core logic of keywords command."""
|
||||
with patch("photools3.cli.LlmSessionCaptionsAndKeywords") as mocked_session, \
|
||||
patch("photools3.cli.Photo") as mocked_photo:
|
||||
|
||||
# Configure session mock
|
||||
session_instance = mocked_session.return_value
|
||||
session_instance.call.return_value = {
|
||||
"caption": "A beautiful test image",
|
||||
"args": ["test", "mock", "image"] # Wait, the code uses 'tags'
|
||||
}
|
||||
# Let me re-check the code in cli.py
|
||||
# result.get("tags", [])
|
||||
session_instance.call.return_value = {
|
||||
"caption": "A beautiful test image",
|
||||
"tags": ["test", "mock", "image"]
|
||||
}
|
||||
|
||||
# Configure photo mock
|
||||
photo_instance = mocked_photo.return_value
|
||||
photo_instance.path = tmp_path / "test_image.jpg"
|
||||
|
||||
yield {
|
||||
"session": session_instance,
|
||||
"photo_class": mocked_photo,
|
||||
"photo_instance": photo_instance
|
||||
}
|
||||
|
||||
@pytest.fixture
|
||||
def dummy_file(tmp_path):
|
||||
"""Fixture to create a dummy file for CLI testing."""
|
||||
dummy_path = tmp_path / "test_image.jpg"
|
||||
dummy_path.write_text("dummy content")
|
||||
return dummy_path
|
||||
|
||||
def test_keywords_command_success(mock_photo_logic, dummy_file):
|
||||
"""Test the 'keywords' command with a valid file path."""
|
||||
# Ensure the mock photo instance points to our dummy file
|
||||
mock_photo_logic["photo_class"].return_value.path = dummy_file
|
||||
|
||||
result = runner.invoke(app, ["keywords", str(dummy_file)])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "Processing: " in result.stdout
|
||||
assert "Caption: A beautiful test image" in result.stdout
|
||||
assert "Tags: test, mock, image" in result.stdout
|
||||
|
||||
def test_keywords_command_file_not_found():
|
||||
"""Test the 'keywords' command with a non-existent file path."""
|
||||
non_existent_file = Path("non_existent_file.jpg")
|
||||
result = runner.invoke(app, ["keywords", str(non_existent_file)])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert f"Error: File '{non_existent_file}' does not exist." in result.stderr
|
||||
|
||||
def test_keywords_command_error_during_processing(mock_photo_logic, dummy_file):
|
||||
"""Test the 'keywords' command when an exception occurs during processing."""
|
||||
# Configure the session mock to raise an exception
|
||||
mock_photo_logic["session"].call.side_effect = Exception("LLM error")
|
||||
|
||||
result = runner.invoke(app, ["keywords", str(dummy_file)])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "Error processing image: LLM error" in result.stderr
|
||||
|
||||
def test_version_command():
|
||||
"""Test the --version option."""
|
||||
result = runner.invoke(app, ["--version"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "photools3 version 0.1.0" in result.stdout
|
||||
Reference in New Issue
Block a user