diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 1dece1ec..2274abfb 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -69,8 +69,8 @@ jobs: with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} - allowed_non_write_users: Copilot,copilot,jules[bot],jules - allowed_bots: "github-actions[bot],copilot[bot],dependabot[bot],copilot,github-actions,gemini[bot],claude[bot],jules[bot]" + allowed_non_write_users: "" + allowed_bots: "github-actions[bot],copilot[bot],dependabot[bot],github-actions,gemini[bot],claude[bot],jules[bot]" trigger_phrase: "@claude" assignee_trigger: claude[bot] label_trigger: claude @@ -105,8 +105,8 @@ jobs: with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} - allowed_non_write_users: Copilot,copilot,jules[bot],jules - allowed_bots: "github-actions[bot],copilot[bot],dependabot[bot],copilot,github-actions,gemini[bot],claude[bot],jules[bot]" + allowed_non_write_users: "" + allowed_bots: "github-actions[bot],copilot[bot],dependabot[bot],github-actions,gemini[bot],claude[bot],jules[bot]" trigger_phrase: "@claude" assignee_trigger: claude label_trigger: claude @@ -142,8 +142,8 @@ jobs: with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} - allowed_non_write_users: Copilot,copilot,jules[bot],jules - allowed_bots: "github-actions[bot],copilot[bot],dependabot[bot],copilot,github-actions,gemini[bot],claude[bot],jules[bot]" + allowed_non_write_users: "" + allowed_bots: "github-actions[bot],copilot[bot],dependabot[bot],github-actions,gemini[bot],claude[bot],jules[bot]" trigger_phrase: "@claude" assignee_trigger: claude label_trigger: claude @@ -181,8 +181,8 @@ jobs: with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} - allowed_non_write_users: Copilot,copilot,jules[bot],jules - allowed_bots: "github-actions[bot],copilot[bot],dependabot[bot],copilot,github-actions,gemini[bot],claude[bot],jules[bot]" + allowed_non_write_users: "" + allowed_bots: "github-actions[bot],copilot[bot],dependabot[bot],github-actions,gemini[bot],claude[bot],jules[bot]" trigger_phrase: "@claude" assignee_trigger: claude label_trigger: claude diff --git a/src/codeweaver/core/types/service_cards.py b/src/codeweaver/core/types/service_cards.py index 546859f2..0d553adb 100644 --- a/src/codeweaver/core/types/service_cards.py +++ b/src/codeweaver/core/types/service_cards.py @@ -1268,11 +1268,11 @@ def _build_data_provider_cards() -> list[ServiceCard]: service_card_factory( "duckduckgo", "data", - lateimport("codeweaver.providers.data.duckduckgo", "duckduckgo_search_tool"), + lateimport("pydantic_ai.common_tools.duckduckgo", "duck_duck_go_search_tool"), lateimport("ddgs.ddgs", "DDGS"), "duckduckgo", metadata=ServiceMetadata( - # provider_cls is duckduckgo_search_tool; called with (client, **extra_kwargs) + # provider_cls is duck_duck_go_search_tool; called with (client, **extra_kwargs) provider_handler=lambda provider_cls, card, client=None, **kwargs: provider_cls( client, max_results=15 ) diff --git a/src/codeweaver/providers/config/clients/multi.py b/src/codeweaver/providers/config/clients/multi.py index 35c15ab1..c7fdd544 100644 --- a/src/codeweaver/providers/config/clients/multi.py +++ b/src/codeweaver/providers/config/clients/multi.py @@ -43,12 +43,12 @@ ) -if has_package("google") is not None: +if has_package("google"): from google.auth.credentials import Credentials as GoogleCredentials else: GoogleCredentials = Any -if has_package("fastembed") is not None or has_package("fastembed_gpu") is not None: +if has_package("fastembed") or has_package("fastembed-gpu"): try: from fastembed.common.types import OnnxProvider except ImportError: @@ -56,14 +56,14 @@ else: OnnxProvider = Any # type: ignore[assignment, misc] -if has_package("torch") is not None: +if has_package("torch"): try: from torch.nn import Module except ImportError: Module = Any # type: ignore[assignment, misc] else: Module = Any # type: ignore[assignment, misc] -if has_package("sentence_transformers") is not None: +if has_package("sentence_transformers"): # SentenceTransformerModelCardData contains these forward references: # - eval_results_dict: dict[SentenceEvaluator, dict[str, Any]] | None # - model: SentenceTransformer | None @@ -521,7 +521,7 @@ def _telemetry_keys(self) -> dict[FilteredKeyT, AnonymityConversion]: # Rebuild Pydantic models to resolve forward references after all imports complete # This is necessary because SentenceTransformerModelCardData contains SentenceEvaluator references if ( - has_package("sentence_transformers") is not None + has_package("sentence_transformers") and not SentenceTransformersClientOptions.__pydantic_complete__ ): # we can rebuild lazily later if this fails diff --git a/src/codeweaver/providers/config/profiles.py b/src/codeweaver/providers/config/profiles.py index b52148ef..5d658f0f 100644 --- a/src/codeweaver/providers/config/profiles.py +++ b/src/codeweaver/providers/config/profiles.py @@ -349,11 +349,11 @@ def _quickstart_default( agent_config=AnthropicAgentModelConfig(anthropic_thinking={"type": "disabled"}), ), ), - data=(TavilyProviderSettings(provider=Provider.TAVILY),) - if has_package("tavily") and Provider.TAVILY.has_env_auth - else (DuckDuckGoProviderSettings(provider=Provider.DUCKDUCKGO),) - if has_package("ddgs") - else (), + data=( + TavilyProviderSettings(provider=Provider.TAVILY) + if has_package("tavily") and Provider.TAVILY.has_env_auth + else DuckDuckGoProviderSettings(provider=Provider.DUCKDUCKGO), + ), vector_store=( QdrantVectorStoreProviderSettings( provider=Provider.QDRANT, diff --git a/src/codeweaver/providers/vector_stores/inmemory.py b/src/codeweaver/providers/vector_stores/inmemory.py index 865567dc..21f58532 100644 --- a/src/codeweaver/providers/vector_stores/inmemory.py +++ b/src/codeweaver/providers/vector_stores/inmemory.py @@ -153,9 +153,6 @@ async def _persist_to_disk(self) -> None: await temp_path.unlink() try: - # Ensure parent directory exists before creating Qdrant client - await temp_path.parent.mkdir(parents=True, exist_ok=True) - # Initialize persistent client at temp path # We use AsyncQdrantClient with path to create local storage dest_client = AsyncQdrantClient(path=str(temp_path)) diff --git a/tests/unit/core/test_discovery.py b/tests/unit/core/test_discovery.py index 53f36534..13b7d20f 100644 --- a/tests/unit/core/test_discovery.py +++ b/tests/unit/core/test_discovery.py @@ -1,88 +1,18 @@ -# SPDX-FileCopyrightText: 2026 Knitli Inc. -# SPDX-FileContributor: Adam Poulemanos -# -# SPDX-License-Identifier: MIT OR Apache-2.0 - -"""Unit tests for core discovery logic.""" - from pathlib import Path from unittest.mock import patch -import pytest - from codeweaver.core.discovery import DiscoveredFile -from codeweaver.core.metadata import ExtCategory - - -pytestmark = [pytest.mark.unit] - - -@pytest.fixture -def temp_project(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path: - """Fixture to provide a temporary project directory and set the environment variable.""" - project_dir = tmp_path / "project" - project_dir.mkdir() - monkeypatch.setenv("CODEWEAVER_PROJECT_PATH", str(project_dir)) - return project_dir - - -def test_absolute_path_when_path_is_absolute() -> None: - """Test absolute_path property when the file path is already absolute.""" - abs_path = Path("/tmp/some_absolute_file.txt").resolve() - df = DiscoveredFile( - path=abs_path, - ext_category=ExtCategory.from_file(abs_path), - project_path=Path("/tmp/project") - ) - result = df.absolute_path - assert result == abs_path -def test_absolute_path_when_path_is_relative_and_project_path_set() -> None: - """Test absolute_path property when the file path is relative and project_path is set.""" - rel_path = Path("src/main.py") - proj_path = Path("/home/user/project") - df = DiscoveredFile( - path=rel_path, - ext_category=ExtCategory.from_file(rel_path), - project_path=proj_path +def test_absolute_path_filenotfound() -> None: + """Test that absolute_path falls back to returning self.path if get_project_path raises FileNotFoundError.""" + # Setup our discovered file + file = DiscoveredFile( + path=Path("some/file.py"), + project_path=None ) - result = df.absolute_path - assert result == proj_path / rel_path - - -def test_absolute_path_when_project_path_is_none_success(temp_project: Path) -> None: - """Test absolute_path property when project_path is falsy and get_project_path succeeds.""" - rel_path = Path("src/main.py") - df = DiscoveredFile( - path=rel_path, - ext_category=ExtCategory.from_file(rel_path), - project_path=temp_project - ) - # The property checks `if self.project_path:`. We can fake this by setting it to empty. - object.__setattr__(df, "project_path", "") - - result = df.absolute_path - - # It should fall back to get_project_path() which is temp_project due to the fixture - assert result == temp_project / rel_path - - -@patch('codeweaver.core.utils.get_project_path') -def test_absolute_path_when_project_path_is_none_filenotfound(mock_get_project_path) -> None: - """Test absolute_path property when project_path is falsy and get_project_path raises FileNotFoundError.""" - mock_get_project_path.side_effect = FileNotFoundError() - - rel_path = Path("src/main.py") - df = DiscoveredFile( - path=rel_path, - ext_category=ExtCategory.from_file(rel_path), - project_path=Path("/tmp") - ) - # The property checks `if self.project_path:`. We can fake this by setting it to empty. - object.__setattr__(df, "project_path", "") - - result = df.absolute_path - # It should catch FileNotFoundError and return self.path - assert result == rel_path + # We want to mock get_project_path to raise FileNotFoundError + with patch("codeweaver.core.utils.get_project_path", side_effect=FileNotFoundError): + # We expect absolute_path to fall back to returning self.path + assert file.absolute_path == Path("some/file.py") diff --git a/tests/unit/test_init.py b/tests/unit/test_init.py deleted file mode 100644 index d6610f2b..00000000 --- a/tests/unit/test_init.py +++ /dev/null @@ -1,131 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Knitli Inc. -# -# SPDX-License-Identifier: MIT OR Apache-2.0 - -import importlib.metadata -import shutil -import subprocess -import sys - -from unittest.mock import MagicMock - -import pytest - -import codeweaver - - -@pytest.fixture -def mock_no_version_file(monkeypatch): - """Remove _version module from sys.modules and codeweaver module. - - A reload of the codeweaver module is necessary because the `get_version()` - function evaluates the imports (which populate its internal values) exactly once - at execution time. If we don't reload the module after changing sys.modules, - it may continue using the previously cached module references. - """ - monkeypatch.delitem(sys.modules, "codeweaver._version", raising=False) - if hasattr(codeweaver, "_version"): - monkeypatch.delattr(codeweaver, "_version", raising=False) - - # We must also clear the internal import mechanisms tracking caching! - # A cleaner approach is simply to re-import get_version explicitly so it bounds to the current env - import importlib - - importlib.reload(codeweaver) - return codeweaver.get_version - - -@pytest.fixture -def mock_no_metadata(monkeypatch, mock_no_version_file): - """Mock importlib.metadata to raise PackageNotFoundError for code-weaver.""" - _original_version = importlib.metadata.version - - def mock_version(pkg_name): - if pkg_name == "code-weaver": - raise importlib.metadata.PackageNotFoundError("code-weaver") - return _original_version(pkg_name) - - monkeypatch.setattr(importlib.metadata, "version", mock_version) - return mock_no_version_file - - -def test_get_version_from_version_file(monkeypatch, mock_no_version_file): - """Test getting version from codeweaver._version.__version__.""" - mock_module = MagicMock() - mock_module.__version__ = "1.2.3-file" - monkeypatch.setitem(sys.modules, "codeweaver._version", mock_module) - - # Need to reload codeweaver because we just changed sys.modules - import importlib - - importlib.reload(codeweaver) - - assert codeweaver.get_version() == "1.2.3-file" - - -def test_get_version_from_importlib_metadata(monkeypatch, mock_no_version_file): - """Test getting version from importlib.metadata.""" - get_version = mock_no_version_file - _original_version = importlib.metadata.version - - def mock_version(pkg_name): - if pkg_name == "code-weaver": - return "1.2.3-metadata" - return _original_version(pkg_name) - - monkeypatch.setattr(importlib.metadata, "version", mock_version) - - assert get_version() == "1.2.3-metadata" - - -def test_get_version_from_git_success(monkeypatch, mock_no_metadata): - """Test getting version from git describe.""" - get_version = mock_no_metadata - monkeypatch.setattr(shutil, "which", lambda cmd: "git" if cmd == "git" else None) - - mock_result = MagicMock() - mock_result.returncode = 0 - mock_result.stdout = "1.2.3-git\n" - - def mock_run(*args, **kwargs): - return mock_result - - monkeypatch.setattr(subprocess, "run", mock_run) - - assert get_version() == "1.2.3-git" - - -def test_get_version_from_git_failure(monkeypatch, mock_no_metadata): - """Test git describe fails and returns 0.0.0.""" - get_version = mock_no_metadata - monkeypatch.setattr(shutil, "which", lambda cmd: "git" if cmd == "git" else None) - - mock_result = MagicMock() - mock_result.returncode = 1 - - def mock_run(*args, **kwargs): - return mock_result - - monkeypatch.setattr(subprocess, "run", mock_run) - - assert get_version() == "0.0.0" - - -def test_get_version_no_git(monkeypatch, mock_no_metadata): - """Test git is not installed, returns 0.0.0.""" - get_version = mock_no_metadata - monkeypatch.setattr(shutil, "which", lambda cmd: None) - - assert get_version() == "0.0.0" - - -def test_get_version_exception(monkeypatch, mock_no_metadata): - """Test general exception in git block returns 0.0.0.""" - get_version = mock_no_metadata - - def mock_which(cmd): - raise RuntimeError("Something went wrong") - - monkeypatch.setattr(shutil, "which", mock_which) - - assert get_version() == "0.0.0"