Skip to content

Commit 12f3cc9

Browse files
InfantLabclaude
andcommitted
fix: restore missing storage modules and unblock install/CI for JOSS review
The published storage package was missing manager.py and the providers/ subpackage: an unanchored `storage/` rule in .gitignore silently excluded them, so fresh clones failed with `No module named videoannotator.storage.manager`. Anchor the rule to `/storage/` and commit the four source files. Also addresses the other reviewer blockers in openjournals/joss-reviews#10182: - bump openai-whisper ==20240930 -> >=20250625 (sdist-only; build deps kept) - repoint reviewer docs from the removed scripts/verify_installation.py to `videoannotator diagnose` - clear pre-existing lint/type failures so CI can go green: ruff fixes, add types-PyYAML/types-requests, and 5 type-only mypy fixes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a24a201 commit 12f3cc9

16 files changed

Lines changed: 333 additions & 42 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ test_output/
165165
data/*
166166
models/
167167
outputs/
168-
storage/
168+
/storage/
169169

170170
# Jupyter Notebook checkpoints
171171
*.ipynb_checkpoints

docs/GETTING_STARTED_REVIEWERS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ curl -LsSf https://astral.sh/uv/install.sh | sh
4141
uv sync
4242

4343
# 4. Verify installation
44-
uv run python scripts/verify_installation.py
44+
uv run videoannotator diagnose
4545
```
4646

4747
**Expected output**: All checks pass ✅ (GPU optional)

docs/installation/troubleshooting.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,13 +237,13 @@ chmod -R u+rw logs/
237237
### Installation Verification Fails
238238

239239
**Symptoms**:
240-
- `scripts/verify_installation.py` reports failures
240+
- `videoannotator diagnose` reports failures
241241

242242
**Solution**:
243243

244244
Run with verbose output to see specific issues:
245245
```bash
246-
uv run python scripts/verify_installation.py --verbose
246+
uv run videoannotator diagnose
247247
```
248248

249249
Common fixes:
@@ -652,7 +652,7 @@ vm_stat # macOS
652652
uv run python -c "import videoannotator; print(videoannotator.__version__)"
653653

654654
# Installation verification
655-
uv run python scripts/verify_installation.py --verbose
655+
uv run videoannotator diagnose
656656

657657
# Check imports
658658
uv run python -c "
@@ -781,7 +781,7 @@ If you can't resolve the issue:
781781
2. **Gather diagnostic information**:
782782
```bash
783783
# Run full diagnostic
784-
uv run python scripts/verify_installation.py --verbose > diagnostic.txt 2>&1
784+
uv run videoannotator diagnose > diagnostic.txt 2>&1
785785

786786
# Include system info
787787
uname -a >> diagnostic.txt

pyproject.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ dependencies = [
3131
"librosa>=0.10.0",
3232
"matplotlib>=3.9.2",
3333
"moviepy>=1.0.3",
34-
# Pin to a stable earlier version to avoid installation metadata issues in CI
35-
"openai-whisper==20240930",
34+
# sdist-only release; built from source at install (see tool.uv.extra-build-dependencies)
35+
"openai-whisper>=20250625",
3636
"numba>=0.60.0", # Ensure Python 3.12 compatibility
3737
"openpyxl",
3838
"pandas>=2.2.2",
@@ -149,7 +149,7 @@ videoannotator = "videoannotator.cli:app"
149149
# uv-native config - empty is fine for now
150150

151151
[tool.uv.extra-build-dependencies]
152-
openai-whisper = ["setuptools==69.0.3", "wheel"]
152+
openai-whisper = ["setuptools", "wheel"]
153153

154154
[[tool.uv.index]]
155155
name = "pytorch-cu124"
@@ -311,6 +311,8 @@ dev = [
311311
"pytest-asyncio>=1.1.0",
312312
"pytest-cov>=4.0.0",
313313
"ruff>=0.14.0",
314+
"types-PyYAML>=6.0.0",
315+
"types-requests>=2.31.0",
314316
]
315317

316318
[[tool.mypy.overrides]]

src/videoannotator/pipelines/audio_processing/laion_voice_pipeline.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,7 @@ def _predict_emotions(self, embedding: torch.Tensor) -> dict[str, Any]:
995995
# Apply softmax across all emotions to get proper probability distribution
996996
if raw_scores:
997997
scores_array = np.array(list(raw_scores.values()))
998-
max_score = np.max(scores_array)
998+
max_score = float(np.max(scores_array))
999999
exp_scores = np.exp(scores_array - max_score)
10001000
softmax_scores = exp_scores / np.sum(exp_scores)
10011001

src/videoannotator/pipelines/face_analysis/face_pipeline.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,8 +463,9 @@ def _detect_faces_opencv(
463463
) -> list[dict[str, Any]]:
464464
"""Detect faces using OpenCV Haar cascades."""
465465
# Load cascade classifier
466+
haarcascades_dir = cv2.data.haarcascades # type: ignore[attr-defined]
466467
face_cascade = cv2.CascadeClassifier(
467-
cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
468+
haarcascades_dir + "haarcascade_frontalface_default.xml"
468469
)
469470

470471
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

src/videoannotator/pipelines/scene_detection/scene_pipeline.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ def _classify_scenes(
231231

232232
# Prepare text prompts
233233
text_prompts = [f"a {prompt}" for prompt in self.config["scene_prompts"]]
234+
assert self.clip_tokenizer is not None
234235
text = self.clip_tokenizer(text_prompts).to(self.device)
235236

236237
classified_segments = []

src/videoannotator/storage/cleanup.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@
88

99
import shutil
1010
from datetime import datetime, timedelta
11-
from pathlib import Path
1211
from typing import Any
1312

14-
from videoannotator.config_env import STORAGE_RETENTION_DAYS, STORAGE_BASE_DIR
13+
from videoannotator.config_env import STORAGE_BASE_DIR, STORAGE_RETENTION_DAYS
1514
from videoannotator.database.models import Job, JobStatus
1615
from videoannotator.storage.file_backend import FileStorageBackend
1716
from videoannotator.utils.logging_config import get_logger
@@ -107,9 +106,7 @@ def find_old_jobs(retention_days: int | None = None) -> list[Job]:
107106
days = retention_days if retention_days is not None else STORAGE_RETENTION_DAYS
108107

109108
if days is None or days <= 0:
110-
raise ValueError(
111-
"Cleanup is disabled (STORAGE_RETENTION_DAYS not set or <= 0)"
112-
)
109+
raise ValueError("Cleanup is disabled (STORAGE_RETENTION_DAYS not set or <= 0)")
113110

114111
cutoff_date = datetime.now() - timedelta(days=days)
115112

@@ -150,7 +147,11 @@ def verify_job_safe_to_delete(job: Job) -> tuple[bool, str]:
150147
return False, "Job has no completion timestamp"
151148

152149
# Check 3: Completion must be in the past (naive comparison for test compatibility)
153-
completed_at = job.completed_at.replace(tzinfo=None) if hasattr(job.completed_at, 'replace') else job.completed_at
150+
completed_at = (
151+
job.completed_at.replace(tzinfo=None)
152+
if hasattr(job.completed_at, "replace")
153+
else job.completed_at
154+
)
154155
if completed_at > datetime.now():
155156
return False, "Job completion timestamp is in the future"
156157

src/videoannotator/storage/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import os
1212
from pathlib import Path
1313

14-
import yaml # type: ignore
14+
import yaml
1515

1616

1717
def get_storage_root() -> Path:
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""Storage manager factory.
2+
3+
This module provides a factory for obtaining the configured storage provider.
4+
"""
5+
6+
from functools import lru_cache
7+
8+
from videoannotator.storage.config import get_storage_root
9+
from videoannotator.storage.providers.base import StorageProvider
10+
from videoannotator.storage.providers.local import LocalStorageProvider
11+
from videoannotator.utils.logging_config import get_logger
12+
13+
logger = get_logger("storage.manager")
14+
15+
16+
@lru_cache
17+
def get_storage_provider() -> StorageProvider:
18+
"""Get the configured storage provider instance.
19+
20+
Returns:
21+
StorageProvider: The singleton storage provider instance.
22+
"""
23+
# In the future, we will read the provider type from config.
24+
# For now, we default to LocalStorageProvider.
25+
26+
root_path = get_storage_root()
27+
logger.info(f"Initializing storage provider with root: {root_path}")
28+
29+
provider = LocalStorageProvider(root_path=root_path)
30+
provider.initialize()
31+
32+
# Validate write permissions
33+
try:
34+
test_file = root_path / ".write_test"
35+
test_file.touch()
36+
test_file.unlink()
37+
except Exception as e:
38+
logger.warning(f"Storage root {root_path} is not writable: {e}")
39+
# We don't raise here to allow read-only scenarios if intended,
40+
# but for a job processor this is likely fatal.
41+
42+
return provider

0 commit comments

Comments
 (0)