Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
version: "latest"

- name: Set up Python
run: uv python install 3.13
run: uv python install 3.14

- name: Install dependencies
run: uv sync --all-extras
Expand All @@ -46,7 +46,7 @@ jobs:
version: "latest"

- name: Set up Python
run: uv python install 3.13
run: uv python install 3.14

- name: Install dependencies
run: uv sync --all-extras
Expand All @@ -66,7 +66,7 @@ jobs:
version: "latest"

- name: Set up Python
run: uv python install 3.13
run: uv python install 3.14

- name: Install dependencies
run: uv sync --all-extras
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed architecture, code style, an
- Use `uv add <package>` (or `uv add --optional dev <package>`) to introduce new dependencies — this updates `pyproject.toml` and `uv.lock` together

### Python Version
- Use Python 3.13.x (pinned in `pyproject.toml`)
- Use Python 3.14.x (pinned in `pyproject.toml`)
- Check version: `uv run python --version`

### Before Running Any Python Code
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This guide covers development setup, architecture, and how to extend Drover.

### Prerequisites

- Python 3.13.x
- Python 3.14.x
- [uv](https://docs.astral.sh/uv/) (package and environment manager)

### Environment Setup
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Drover uses LLMs to analyze documents and suggest consistent, policy-compliant f

### Prerequisites

- Python 3.13.x
- Python 3.14.x
- [uv](https://docs.astral.sh/uv/) (package and environment manager)
- [Ollama](https://ollama.ai/) (for local inference) or API keys for cloud providers

Expand Down
2 changes: 1 addition & 1 deletion docs/increments.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Establish automated quality gates and consistent development workflows.

### GitHub Actions CI Pipeline
- [ ] Create workflow for lint, type-check, and test on PR
- [ ] Add matrix testing across OS platforms (Ubuntu, macOS, Windows) with Python 3.13
- [ ] Add matrix testing across OS platforms (Ubuntu, macOS, Windows) with Python 3.14
- [ ] Configure dependency caching for faster builds
- [ ] Add security scanning (pip-audit, bandit)
- [ ] Set up coverage reporting with threshold enforcement
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "drover"
version = "0.2.0"
description = "Document classification CLI that classifies files into organized folder structures"
readme = "README.md"
requires-python = ">=3.13,<3.14"
requires-python = ">=3.14"
license = "AGPL-3.0-or-later"
dependencies = [
"langchain>=0.3,<1.0",
Expand Down Expand Up @@ -57,7 +57,7 @@ markers = [
]

[tool.ruff]
target-version = "py313"
target-version = "py314"
src = ["src", "tests"]

[tool.ruff.lint]
Expand Down Expand Up @@ -90,7 +90,7 @@ known-first-party = ["drover"]
"tests/**/*.py" = ["ARG001", "ARG002", "ERA001"]

[tool.mypy]
python_version = "3.13"
python_version = "3.14"
strict = true
warn_return_any = true
warn_unused_configs = true
Expand Down
2 changes: 1 addition & 1 deletion smoke/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Exit code: 0 if every non-skipped test passed; 1 if any failed.
"duration_ms": 134812,
"environment": {
"drover_version": "0.1.0",
"python_version": "3.13.x",
"python_version": "3.14.x",
"platform": "darwin",
"ollama_available": true,
"ollama_endpoint": "http://127.0.0.1:11434",
Expand Down
8 changes: 5 additions & 3 deletions src/drover/actions/base.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Base abstractions for file actions based on classification results."""

from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Protocol
from typing import TYPE_CHECKING, Any, Protocol

from drover.models import ClassificationResult
if TYPE_CHECKING:
from pathlib import Path

from drover.models import ClassificationResult


@dataclass
Expand Down
12 changes: 7 additions & 5 deletions src/drover/classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,22 @@
import os
import re
import socket
from collections.abc import Callable
from importlib.resources import files
from pathlib import Path
from typing import TYPE_CHECKING, Any, cast

if TYPE_CHECKING:
from collections.abc import Callable
from importlib.resources.abc import Traversable
from pathlib import Path

from langchain_core.language_models import BaseChatModel
from langchain_core.runnables import Runnable

from drover.taxonomy.base import BaseTaxonomy

import yaml
from json_repair import repair_json
from langchain_core.language_models import BaseChatModel
from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
from langchain_core.runnables import Runnable
from langchain_ollama import ChatOllama
from langchain_openai import ChatOpenAI
from pydantic import ValidationError
Expand All @@ -39,7 +42,6 @@
from drover.logging import get_logger
from drover.metrics import create_metrics_callback
from drover.models import RawClassification
from drover.taxonomy.base import BaseTaxonomy

logger = get_logger(__name__)

Expand Down
4 changes: 2 additions & 2 deletions src/drover/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def _git_head_marker() -> str:
text=True,
stderr=subprocess.DEVNULL,
).strip()
except (subprocess.CalledProcessError, FileNotFoundError, OSError):
except subprocess.CalledProcessError, FileNotFoundError, OSError:
return ""
try:
dirty = (
Expand All @@ -68,7 +68,7 @@ def _git_head_marker() -> str:
).returncode
!= 0
)
except (FileNotFoundError, OSError):
except FileNotFoundError, OSError:
dirty = False
return f"{head}-dirty" if dirty else head

Expand Down
13 changes: 8 additions & 5 deletions src/drover/evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,20 @@
"""

import json
from collections.abc import Sequence
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, Field

from drover.classifier import DocumentClassifier
from drover.loader import DoclingLoader
from drover.logging import get_logger
from drover.models import RawClassification

if TYPE_CHECKING:
from collections.abc import Sequence

from drover.classifier import DocumentClassifier
from drover.loader import DoclingLoader
from drover.models import RawClassification

logger = get_logger(__name__)

Expand Down
6 changes: 4 additions & 2 deletions src/drover/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
"""

import time
from typing import Any
from typing import TYPE_CHECKING, Any

from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.outputs import LLMResult
from pydantic import BaseModel, Field

if TYPE_CHECKING:
from langchain_core.outputs import LLMResult

MODEL_PRICING: dict[str, tuple[float, float]] = {
"gpt-4o": (2.50, 10.00),
"gpt-4o-mini": (0.15, 0.60),
Expand Down
8 changes: 5 additions & 3 deletions src/drover/models.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"""Pydantic models for document classification."""

from enum import StrEnum
from pathlib import Path
from typing import Any
from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, Field

if TYPE_CHECKING:
from pathlib import Path


class ErrorCode(StrEnum):
"""Error codes for classification failures."""
Expand Down Expand Up @@ -65,7 +67,7 @@ class ClassificationErrorResult(BaseModel):
@classmethod
def from_exception(
cls, filename: str | Path, code: ErrorCode, exception: Exception
) -> "ClassificationErrorResult":
) -> ClassificationErrorResult:
"""Create error result from an exception."""
return cls(
original=str(filename),
Expand Down
6 changes: 5 additions & 1 deletion src/drover/naming/loader.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
"""Naming policy plugin discovery and loading."""

from drover.naming.base import BaseNamingPolicy
from typing import TYPE_CHECKING

from drover.naming.nara import NARAPolicyNaming

if TYPE_CHECKING:
from drover.naming.base import BaseNamingPolicy

_BUILTIN_POLICIES: dict[str, type[BaseNamingPolicy]] = {
"nara": NARAPolicyNaming,
}
Expand Down
10 changes: 7 additions & 3 deletions src/drover/path_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@
"""

import re
from pathlib import Path
from typing import TYPE_CHECKING

from drover.models import ClassificationResult, PathConstraints, RawClassification
from drover.naming.base import BaseNamingPolicy
from drover.taxonomy.base import BaseTaxonomy

if TYPE_CHECKING:
from pathlib import Path

from drover.naming.base import BaseNamingPolicy
from drover.taxonomy.base import BaseTaxonomy


class PathConstraintError(Exception):
Expand Down
6 changes: 5 additions & 1 deletion src/drover/taxonomy/loader.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
"""Taxonomy plugin discovery and loading."""

from drover.taxonomy.base import BaseTaxonomy
from typing import TYPE_CHECKING

from drover.taxonomy.household import HouseholdTaxonomy

if TYPE_CHECKING:
from drover.taxonomy.base import BaseTaxonomy

_BUILTIN_TAXONOMIES: dict[str, type[BaseTaxonomy]] = {
"household": HouseholdTaxonomy,
}
Expand Down
7 changes: 5 additions & 2 deletions tests/integration/test_classify_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@
"""

from pathlib import Path
from typing import TYPE_CHECKING

import pytest

from drover.classifier import DocumentClassifier
from drover.loader import DoclingLoader
from drover.models import RawClassification
from drover.naming.nara import NARAPolicyNaming
from drover.path_builder import PathBuilder
from drover.taxonomy.household import HouseholdTaxonomy

if TYPE_CHECKING:
from drover.classifier import DocumentClassifier
from drover.loader import DoclingLoader


@pytest.mark.integration
class TestClassificationPipeline:
Expand Down
6 changes: 4 additions & 2 deletions tests/test_actions_base.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
"""Tests for the action infrastructure."""

from pathlib import Path

import pytest
from typing import TYPE_CHECKING

from drover.actions.base import ActionPlan, ActionResult
from drover.actions.runner import ActionRunner
from drover.config import DroverConfig, ErrorMode
from drover.models import ClassificationResult

if TYPE_CHECKING:
import pytest


class MockAction:
"""Mock action for testing."""
Expand Down
5 changes: 4 additions & 1 deletion tests/test_classifier_docling.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
that the max_prompt_chars cap is applied correctly.
"""

import pytest
from typing import TYPE_CHECKING

from drover.classifier import DocumentClassifier
from drover.config import AIProvider, TaxonomyMode
from drover.models import RawClassification
from drover.taxonomy.household import HouseholdTaxonomy

if TYPE_CHECKING:
import pytest


class _StubStructuredLLM:
"""Records the messages passed to the structured LLM."""
Expand Down
9 changes: 6 additions & 3 deletions tests/test_service_debug_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@
"""

import json
from pathlib import Path
from types import SimpleNamespace
from typing import TYPE_CHECKING
from unittest.mock import patch

import pytest

from drover.loader import DoclingLoader, LoadedDocument

if TYPE_CHECKING:
from pathlib import Path

import pytest


def _patch_classifier(service: object, monkeypatch: pytest.MonkeyPatch) -> None:
"""Replace the classifier with a no-op fake so tests stay LLM-free."""
Expand Down
5 changes: 4 additions & 1 deletion tests/test_tag_manager.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Tests for TagManager and tag operations."""

import sys
from pathlib import Path
from typing import TYPE_CHECKING

import pytest

Expand All @@ -14,6 +14,9 @@
)
from drover.models import ClassificationResult

if TYPE_CHECKING:
from pathlib import Path

# Skip all tests in this module on non-macOS platforms
pytestmark = pytest.mark.skipif(
sys.platform != "darwin",
Expand Down
Loading
Loading