From dc9834278cd0d107312e213f606fd53ab9083cf5 Mon Sep 17 00:00:00 2001 From: Filip Christiansen <22807962+filipchristiansen@users.noreply.github.com> Date: Wed, 15 Jan 2025 09:44:59 +0100 Subject: [PATCH] Refactor codebase into core and server subpackages - Moved shared logic (config, exceptions, ignore_patterns, etc.) into `core/`. - Created `server/` to host FastAPI modules, static files, and templates. - Updated import paths for CLI and tests to reference `core.*` and `server.*`. - Changed static references from `"/static"` to `"/server/static"` and templates from `"/templates"` to `"/server/templates"`. - Adjusted references in `base.jinja`, `server/main.py`, `server/query_processor.py`, and routers to reflect the new server paths. - Ensured Docker builds now correctly serve static and template files from `server/`. --- Dockerfile | 2 +- src/core/__init__.py | 0 src/{ => core}/config.py | 0 src/{gitingest => core}/exceptions.py | 0 src/{gitingest => core}/ignore_patterns.py | 0 src/{gitingest => core}/notebook_utils.py | 2 +- src/{gitingest => core}/query_ingestion.py | 13 +++---- src/{gitingest => core}/query_parser.py | 10 +++--- src/{gitingest => core}/repository_clone.py | 2 +- src/{gitingest => core}/repository_ingest.py | 8 ++--- src/{gitingest => core}/utils.py | 2 +- src/gitingest/__init__.py | 8 ++--- src/gitingest/cli.py | 4 +-- src/routers/__init__.py | 7 ---- src/server/__init__.py | 0 src/{ => server}/main.py | 12 +++---- src/{ => server}/query_processor.py | 12 +++---- src/server/routers/__init__.py | 7 ++++ src/{ => server}/routers/download.py | 2 +- src/{ => server}/routers/dynamic.py | 6 ++-- src/{ => server}/routers/index.py | 8 ++--- src/{ => server}/server_utils.py | 0 src/{ => server}/static/apple-touch-icon.png | Bin src/{ => server}/static/favicon-64.png | Bin src/{ => server}/static/favicon.ico | Bin src/{ => server}/static/favicon.svg | 0 src/{ => server}/static/js/utils.js | 0 src/{ => server}/static/og-image.png | Bin src/{ => server}/static/robots.txt | 0 src/{ => server}/templates/api.jinja | 0 src/{ => server}/templates/base.jinja | 12 +++---- .../templates/components/footer.jinja | 0 .../templates/components/git_form.jinja | 0 .../templates/components/navbar.jinja | 0 .../templates/components/result.jinja | 0 src/{ => server}/templates/git.jinja | 0 src/{ => server}/templates/index.jinja | 0 tests/conftest.py | 2 +- tests/core/__init__.py | 0 .../query_parser/test_git_host_agnostic.py | 2 +- .../query_parser/test_query_parser.py | 34 +++++++++--------- tests/{ => core}/test_notebook_utils.py | 2 +- tests/{ => core}/test_query_ingestion.py | 8 ++--- tests/{ => core}/test_repository_clone.py | 34 +++++++++--------- 44 files changed, 98 insertions(+), 101 deletions(-) create mode 100644 src/core/__init__.py rename src/{ => core}/config.py (100%) rename src/{gitingest => core}/exceptions.py (100%) rename src/{gitingest => core}/ignore_patterns.py (100%) rename src/{gitingest => core}/notebook_utils.py (98%) rename src/{gitingest => core}/query_ingestion.py (98%) rename src/{gitingest => core}/query_parser.py (98%) rename src/{gitingest => core}/repository_clone.py (99%) rename src/{gitingest => core}/repository_ingest.py (93%) rename src/{gitingest => core}/utils.py (96%) delete mode 100644 src/routers/__init__.py create mode 100644 src/server/__init__.py rename src/{ => server}/main.py (95%) rename src/{ => server}/query_processor.py (95%) create mode 100644 src/server/routers/__init__.py rename src/{ => server}/routers/download.py (98%) rename src/{ => server}/routers/dynamic.py (94%) rename src/{ => server}/routers/index.py (93%) rename src/{ => server}/server_utils.py (100%) rename src/{ => server}/static/apple-touch-icon.png (100%) rename src/{ => server}/static/favicon-64.png (100%) rename src/{ => server}/static/favicon.ico (100%) rename src/{ => server}/static/favicon.svg (100%) rename src/{ => server}/static/js/utils.js (100%) rename src/{ => server}/static/og-image.png (100%) rename src/{ => server}/static/robots.txt (100%) rename src/{ => server}/templates/api.jinja (100%) rename src/{ => server}/templates/base.jinja (83%) rename src/{ => server}/templates/components/footer.jinja (100%) rename src/{ => server}/templates/components/git_form.jinja (100%) rename src/{ => server}/templates/components/navbar.jinja (100%) rename src/{ => server}/templates/components/result.jinja (100%) rename src/{ => server}/templates/git.jinja (100%) rename src/{ => server}/templates/index.jinja (100%) create mode 100644 tests/core/__init__.py rename tests/{ => core}/query_parser/test_git_host_agnostic.py (98%) rename tests/{ => core}/query_parser/test_query_parser.py (91%) rename tests/{ => core}/test_notebook_utils.py (99%) rename tests/{ => core}/test_query_ingestion.py (95%) rename tests/{ => core}/test_repository_clone.py (84%) diff --git a/Dockerfile b/Dockerfile index cb0eab8..63577a9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,4 +41,4 @@ USER appuser EXPOSE 8000 -CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] +CMD ["python", "-m", "uvicorn", "server.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/src/core/__init__.py b/src/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/config.py b/src/core/config.py similarity index 100% rename from src/config.py rename to src/core/config.py diff --git a/src/gitingest/exceptions.py b/src/core/exceptions.py similarity index 100% rename from src/gitingest/exceptions.py rename to src/core/exceptions.py diff --git a/src/gitingest/ignore_patterns.py b/src/core/ignore_patterns.py similarity index 100% rename from src/gitingest/ignore_patterns.py rename to src/core/ignore_patterns.py diff --git a/src/gitingest/notebook_utils.py b/src/core/notebook_utils.py similarity index 98% rename from src/gitingest/notebook_utils.py rename to src/core/notebook_utils.py index 1a385ca..192506b 100644 --- a/src/gitingest/notebook_utils.py +++ b/src/core/notebook_utils.py @@ -6,7 +6,7 @@ from pathlib import Path from typing import Any -from gitingest.exceptions import InvalidNotebookError +from core.exceptions import InvalidNotebookError def process_notebook(file: Path, include_output: bool = True) -> str: diff --git a/src/gitingest/query_ingestion.py b/src/core/query_ingestion.py similarity index 98% rename from src/gitingest/query_ingestion.py rename to src/core/query_ingestion.py index a6f94d2..bee3e19 100644 --- a/src/gitingest/query_ingestion.py +++ b/src/core/query_ingestion.py @@ -6,15 +6,10 @@ import tiktoken -from config import MAX_DIRECTORY_DEPTH, MAX_FILES, MAX_TOTAL_SIZE_BYTES -from gitingest.exceptions import ( - AlreadyVisitedError, - InvalidNotebookError, - MaxFileSizeReachedError, - MaxFilesReachedError, -) -from gitingest.notebook_utils import process_notebook -from gitingest.query_parser import ParsedQuery +from core.config import MAX_DIRECTORY_DEPTH, MAX_FILES, MAX_TOTAL_SIZE_BYTES +from core.exceptions import AlreadyVisitedError, InvalidNotebookError, MaxFileSizeReachedError, MaxFilesReachedError +from core.notebook_utils import process_notebook +from core.query_parser import ParsedQuery def _should_include(path: Path, base_path: Path, include_patterns: set[str]) -> bool: diff --git a/src/gitingest/query_parser.py b/src/core/query_parser.py similarity index 98% rename from src/gitingest/query_parser.py rename to src/core/query_parser.py index 435a799..0d2a20d 100644 --- a/src/gitingest/query_parser.py +++ b/src/core/query_parser.py @@ -9,10 +9,10 @@ from pathlib import Path from urllib.parse import unquote, urlparse -from config import MAX_FILE_SIZE, TMP_BASE_PATH -from gitingest.exceptions import InvalidPatternError -from gitingest.ignore_patterns import DEFAULT_IGNORE_PATTERNS -from gitingest.repository_clone import _check_repo_exists, fetch_remote_branch_list +from core.config import MAX_FILE_SIZE, TMP_BASE_PATH +from core.exceptions import InvalidPatternError +from core.ignore_patterns import DEFAULT_IGNORE_PATTERNS +from core.repository_clone import _check_repo_exists, fetch_remote_branch_list HEX_DIGITS: set[str] = set(string.hexdigits) @@ -227,7 +227,7 @@ async def _configure_branch_and_subpath(remaining_parts: list[str], url: str) -> # Fetch the list of branches from the remote repository branches: list[str] = await fetch_remote_branch_list(url) except RuntimeError as e: - warnings.warn(f"Warning: Failed to fetch branch list: {e}") + warnings.warn(f"Warning: Failed to fetch branch list: {e}", UserWarning) return remaining_parts.pop(0) branch = [] diff --git a/src/gitingest/repository_clone.py b/src/core/repository_clone.py similarity index 99% rename from src/gitingest/repository_clone.py rename to src/core/repository_clone.py index 4adfcd9..64b0cdb 100644 --- a/src/gitingest/repository_clone.py +++ b/src/core/repository_clone.py @@ -3,7 +3,7 @@ import asyncio from dataclasses import dataclass -from gitingest.utils import async_timeout +from core.utils import async_timeout TIMEOUT: int = 20 diff --git a/src/gitingest/repository_ingest.py b/src/core/repository_ingest.py similarity index 93% rename from src/gitingest/repository_ingest.py rename to src/core/repository_ingest.py index 64b33eb..49910f0 100644 --- a/src/gitingest/repository_ingest.py +++ b/src/core/repository_ingest.py @@ -4,10 +4,10 @@ import inspect import shutil -from config import TMP_BASE_PATH -from gitingest.query_ingestion import run_ingest_query -from gitingest.query_parser import ParsedQuery, parse_query -from gitingest.repository_clone import CloneConfig, clone_repo +from core.config import TMP_BASE_PATH +from core.query_ingestion import run_ingest_query +from core.query_parser import ParsedQuery, parse_query +from core.repository_clone import CloneConfig, clone_repo async def ingest( diff --git a/src/gitingest/utils.py b/src/core/utils.py similarity index 96% rename from src/gitingest/utils.py rename to src/core/utils.py index 3c28da8..51590f7 100644 --- a/src/gitingest/utils.py +++ b/src/core/utils.py @@ -5,7 +5,7 @@ from collections.abc import Awaitable, Callable from typing import ParamSpec, TypeVar -from gitingest.exceptions import AsyncTimeoutError +from core.exceptions import AsyncTimeoutError T = TypeVar("T") P = ParamSpec("P") diff --git a/src/gitingest/__init__.py b/src/gitingest/__init__.py index 692de60..68f6646 100644 --- a/src/gitingest/__init__.py +++ b/src/gitingest/__init__.py @@ -1,8 +1,8 @@ """ Gitingest: A package for ingesting data from Git repositories. """ -from gitingest.query_ingestion import run_ingest_query -from gitingest.query_parser import parse_query -from gitingest.repository_clone import clone_repo -from gitingest.repository_ingest import ingest +from core.query_ingestion import run_ingest_query +from core.query_parser import parse_query +from core.repository_clone import clone_repo +from core.repository_ingest import ingest __all__ = ["run_ingest_query", "clone_repo", "parse_query", "ingest"] diff --git a/src/gitingest/cli.py b/src/gitingest/cli.py index a21a453..50b4d71 100644 --- a/src/gitingest/cli.py +++ b/src/gitingest/cli.py @@ -6,8 +6,8 @@ import click -from config import MAX_FILE_SIZE -from gitingest.repository_ingest import ingest +from core.config import MAX_FILE_SIZE +from core.repository_ingest import ingest @click.command() diff --git a/src/routers/__init__.py b/src/routers/__init__.py deleted file mode 100644 index d8d2409..0000000 --- a/src/routers/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" This module contains the routers for the FastAPI application. """ - -from routers.download import router as download -from routers.dynamic import router as dynamic -from routers.index import router as index - -__all__ = ["download", "dynamic", "index"] diff --git a/src/server/__init__.py b/src/server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main.py b/src/server/main.py similarity index 95% rename from src/main.py rename to src/server/main.py index 556b3e1..cd0f846 100644 --- a/src/main.py +++ b/src/server/main.py @@ -17,9 +17,9 @@ from slowapi.errors import RateLimitExceeded from starlette.middleware.trustedhost import TrustedHostMiddleware -from config import DELETE_REPO_AFTER, TMP_BASE_PATH -from routers import download, dynamic, index -from server_utils import limiter +from core.config import DELETE_REPO_AFTER, TMP_BASE_PATH +from server.routers import download, dynamic, index +from server.server_utils import limiter # Load environment variables from .env file load_dotenv() @@ -156,7 +156,7 @@ async def rate_limit_exception_handler(request: Request, exc: Exception) -> Resp app.add_exception_handler(RateLimitExceeded, rate_limit_exception_handler) # Mount static files to serve CSS, JS, and other static assets -app.mount("/static", StaticFiles(directory="static"), name="static") +app.mount("/server/static", StaticFiles(directory="server/static"), name="static") # Set up API analytics middleware if an API key is provided if app_analytics_key := os.getenv("API_ANALYTICS_KEY"): @@ -175,7 +175,7 @@ async def rate_limit_exception_handler(request: Request, exc: Exception) -> Resp app.add_middleware(TrustedHostMiddleware, allowed_hosts=allowed_hosts) # Set up template rendering -templates = Jinja2Templates(directory="templates") +templates = Jinja2Templates(directory="server/templates") @app.get("/health") @@ -235,7 +235,7 @@ async def robots() -> FileResponse: FileResponse The `robots.txt` file located in the static directory. """ - return FileResponse("static/robots.txt") + return FileResponse("server/static/robots.txt") # Include routers for modular endpoints diff --git a/src/query_processor.py b/src/server/query_processor.py similarity index 95% rename from src/query_processor.py rename to src/server/query_processor.py index 62f1c83..8d54d0f 100644 --- a/src/query_processor.py +++ b/src/server/query_processor.py @@ -6,13 +6,13 @@ from fastapi.templating import Jinja2Templates from starlette.templating import _TemplateResponse -from config import EXAMPLE_REPOS, MAX_DISPLAY_SIZE -from gitingest.query_ingestion import run_ingest_query -from gitingest.query_parser import ParsedQuery, parse_query -from gitingest.repository_clone import CloneConfig, clone_repo -from server_utils import Colors, log_slider_to_size +from core.config import EXAMPLE_REPOS, MAX_DISPLAY_SIZE +from core.query_ingestion import run_ingest_query +from core.query_parser import ParsedQuery, parse_query +from core.repository_clone import CloneConfig, clone_repo +from server.server_utils import Colors, log_slider_to_size -templates = Jinja2Templates(directory="templates") +templates = Jinja2Templates(directory="server/templates") async def process_query( diff --git a/src/server/routers/__init__.py b/src/server/routers/__init__.py new file mode 100644 index 0000000..ae6666b --- /dev/null +++ b/src/server/routers/__init__.py @@ -0,0 +1,7 @@ +""" This module contains the routers for the FastAPI application. """ + +from server.routers.download import router as download +from server.routers.dynamic import router as dynamic +from server.routers.index import router as index + +__all__ = ["download", "dynamic", "index"] diff --git a/src/routers/download.py b/src/server/routers/download.py similarity index 98% rename from src/routers/download.py rename to src/server/routers/download.py index b4da647..d6385f2 100644 --- a/src/routers/download.py +++ b/src/server/routers/download.py @@ -3,7 +3,7 @@ from fastapi import APIRouter, HTTPException from fastapi.responses import Response -from config import TMP_BASE_PATH +from core.config import TMP_BASE_PATH router = APIRouter() diff --git a/src/routers/dynamic.py b/src/server/routers/dynamic.py similarity index 94% rename from src/routers/dynamic.py rename to src/server/routers/dynamic.py index 0787fbf..d08836a 100644 --- a/src/routers/dynamic.py +++ b/src/server/routers/dynamic.py @@ -4,11 +4,11 @@ from fastapi.responses import HTMLResponse from fastapi.templating import Jinja2Templates -from query_processor import process_query -from server_utils import limiter +from server.query_processor import process_query +from server.server_utils import limiter router = APIRouter() -templates = Jinja2Templates(directory="templates") +templates = Jinja2Templates(directory="server/templates") @router.get("/{full_path:path}") diff --git a/src/routers/index.py b/src/server/routers/index.py similarity index 93% rename from src/routers/index.py rename to src/server/routers/index.py index b338c30..107bfff 100644 --- a/src/routers/index.py +++ b/src/server/routers/index.py @@ -4,12 +4,12 @@ from fastapi.responses import HTMLResponse from fastapi.templating import Jinja2Templates -from config import EXAMPLE_REPOS -from query_processor import process_query -from server_utils import limiter +from core.config import EXAMPLE_REPOS +from server.query_processor import process_query +from server.server_utils import limiter router = APIRouter() -templates = Jinja2Templates(directory="templates") +templates = Jinja2Templates(directory="server/templates") @router.get("/", response_class=HTMLResponse) diff --git a/src/server_utils.py b/src/server/server_utils.py similarity index 100% rename from src/server_utils.py rename to src/server/server_utils.py diff --git a/src/static/apple-touch-icon.png b/src/server/static/apple-touch-icon.png similarity index 100% rename from src/static/apple-touch-icon.png rename to src/server/static/apple-touch-icon.png diff --git a/src/static/favicon-64.png b/src/server/static/favicon-64.png similarity index 100% rename from src/static/favicon-64.png rename to src/server/static/favicon-64.png diff --git a/src/static/favicon.ico b/src/server/static/favicon.ico similarity index 100% rename from src/static/favicon.ico rename to src/server/static/favicon.ico diff --git a/src/static/favicon.svg b/src/server/static/favicon.svg similarity index 100% rename from src/static/favicon.svg rename to src/server/static/favicon.svg diff --git a/src/static/js/utils.js b/src/server/static/js/utils.js similarity index 100% rename from src/static/js/utils.js rename to src/server/static/js/utils.js diff --git a/src/static/og-image.png b/src/server/static/og-image.png similarity index 100% rename from src/static/og-image.png rename to src/server/static/og-image.png diff --git a/src/static/robots.txt b/src/server/static/robots.txt similarity index 100% rename from src/static/robots.txt rename to src/server/static/robots.txt diff --git a/src/templates/api.jinja b/src/server/templates/api.jinja similarity index 100% rename from src/templates/api.jinja rename to src/server/templates/api.jinja diff --git a/src/templates/base.jinja b/src/server/templates/base.jinja similarity index 83% rename from src/templates/base.jinja rename to src/server/templates/base.jinja index a6e30bf..99b99a9 100644 --- a/src/templates/base.jinja +++ b/src/server/templates/base.jinja @@ -3,7 +3,7 @@ - + @@ -11,14 +11,14 @@ content="Gitingest, AI tools, LLM integration, Ingest, Digest, Context, Prompt, Git workflow, codebase extraction, Git repository, Git automation, Summarize, prompt-friendly"> - + + href="/server/static/favicon-64.png"> + href="/server/static/apple-touch-icon.png"> @@ -31,12 +31,12 @@ content="Replace 'hub' with 'ingest' in any GitHub URL for a prompt-friendly text."> - + {% block title %}Gitingest{% endblock %} - + {% block extra_head %}{% endblock %} diff --git a/src/templates/components/footer.jinja b/src/server/templates/components/footer.jinja similarity index 100% rename from src/templates/components/footer.jinja rename to src/server/templates/components/footer.jinja diff --git a/src/templates/components/git_form.jinja b/src/server/templates/components/git_form.jinja similarity index 100% rename from src/templates/components/git_form.jinja rename to src/server/templates/components/git_form.jinja diff --git a/src/templates/components/navbar.jinja b/src/server/templates/components/navbar.jinja similarity index 100% rename from src/templates/components/navbar.jinja rename to src/server/templates/components/navbar.jinja diff --git a/src/templates/components/result.jinja b/src/server/templates/components/result.jinja similarity index 100% rename from src/templates/components/result.jinja rename to src/server/templates/components/result.jinja diff --git a/src/templates/git.jinja b/src/server/templates/git.jinja similarity index 100% rename from src/templates/git.jinja rename to src/server/templates/git.jinja diff --git a/src/templates/index.jinja b/src/server/templates/index.jinja similarity index 100% rename from src/templates/index.jinja rename to src/server/templates/index.jinja diff --git a/tests/conftest.py b/tests/conftest.py index c11ee72..099930a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,7 +6,7 @@ import pytest -from gitingest.query_parser import ParsedQuery +from core.query_parser import ParsedQuery @pytest.fixture diff --git a/tests/core/__init__.py b/tests/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/query_parser/test_git_host_agnostic.py b/tests/core/query_parser/test_git_host_agnostic.py similarity index 98% rename from tests/query_parser/test_git_host_agnostic.py rename to tests/core/query_parser/test_git_host_agnostic.py index 9831362..330b5a8 100644 --- a/tests/query_parser/test_git_host_agnostic.py +++ b/tests/core/query_parser/test_git_host_agnostic.py @@ -2,7 +2,7 @@ import pytest -from gitingest.query_parser import parse_query +from core.query_parser import parse_query @pytest.mark.parametrize( diff --git a/tests/query_parser/test_query_parser.py b/tests/core/query_parser/test_query_parser.py similarity index 91% rename from tests/query_parser/test_query_parser.py rename to tests/core/query_parser/test_query_parser.py index fc5fb0a..2766485 100644 --- a/tests/query_parser/test_query_parser.py +++ b/tests/core/query_parser/test_query_parser.py @@ -5,8 +5,8 @@ import pytest -from gitingest.ignore_patterns import DEFAULT_IGNORE_PATTERNS -from gitingest.query_parser import _parse_patterns, _parse_repo_source, parse_query +from core.ignore_patterns import DEFAULT_IGNORE_PATTERNS +from core.query_parser import _parse_patterns, _parse_repo_source, parse_query async def test_parse_url_valid_https() -> None: @@ -111,11 +111,9 @@ async def test_parse_url_with_subpaths() -> None: Verifies that user name, repository name, branch, and subpath are correctly extracted. """ url = "https://github.com/user/repo/tree/main/subdir/file" - with patch("gitingest.repository_clone._run_git_command", new_callable=AsyncMock) as mock_run_git_command: + with patch("core.repository_clone._run_git_command", new_callable=AsyncMock) as mock_run_git_command: mock_run_git_command.return_value = (b"refs/heads/main\nrefs/heads/dev\nrefs/heads/feature-branch\n", b"") - with patch( - "gitingest.repository_clone.fetch_remote_branch_list", new_callable=AsyncMock - ) as mock_fetch_branches: + with patch("core.repository_clone.fetch_remote_branch_list", new_callable=AsyncMock) as mock_fetch_branches: mock_fetch_branches.return_value = ["main", "dev", "feature-branch"] parsed_query = await _parse_repo_source(url) assert parsed_query.user_name == "user" @@ -237,11 +235,9 @@ async def test_parse_url_branch_and_commit_distinction() -> None: url_branch = "https://github.com/user/repo/tree/main" url_commit = "https://github.com/user/repo/tree/abcd1234abcd1234abcd1234abcd1234abcd1234" - with patch("gitingest.repository_clone._run_git_command", new_callable=AsyncMock) as mock_run_git_command: + with patch("core.repository_clone._run_git_command", new_callable=AsyncMock) as mock_run_git_command: mock_run_git_command.return_value = (b"refs/heads/main\nrefs/heads/dev\nrefs/heads/feature-branch\n", b"") - with patch( - "gitingest.repository_clone.fetch_remote_branch_list", new_callable=AsyncMock - ) as mock_fetch_branches: + with patch("core.repository_clone.fetch_remote_branch_list", new_callable=AsyncMock) as mock_fetch_branches: mock_fetch_branches.return_value = ["main", "dev", "feature-branch"] parsed_query_with_branch = await _parse_repo_source(url_branch) @@ -310,13 +306,19 @@ async def test_parse_repo_source_with_failed_git_command(url, expected_branch, e Test `_parse_repo_source` when git command fails. Verifies that the function returns the first path component as the branch. """ - with patch("gitingest.repository_clone.fetch_remote_branch_list", new_callable=AsyncMock) as mock_fetch_branches: + with patch("core.repository_clone.fetch_remote_branch_list", new_callable=AsyncMock) as mock_fetch_branches: mock_fetch_branches.side_effect = Exception("Failed to fetch branch list") - parsed_query = await _parse_repo_source(url) + with pytest.warns( + UserWarning, + match="Warning: Failed to fetch branch list: Git command failed: " + "git ls-remote --heads https://github.com/user/repo", + ): - assert parsed_query.branch == expected_branch - assert parsed_query.subpath == expected_subpath + parsed_query = await _parse_repo_source(url) + + assert parsed_query.branch == expected_branch + assert parsed_query.subpath == expected_subpath @pytest.mark.asyncio @@ -333,8 +335,8 @@ async def test_parse_repo_source_with_failed_git_command(url, expected_branch, e ) async def test_parse_repo_source_with_various_url_patterns(url, expected_branch, expected_subpath): with ( - patch("gitingest.repository_clone._run_git_command", new_callable=AsyncMock) as mock_run_git_command, - patch("gitingest.repository_clone.fetch_remote_branch_list", new_callable=AsyncMock) as mock_fetch_branches, + patch("core.repository_clone._run_git_command", new_callable=AsyncMock) as mock_run_git_command, + patch("core.repository_clone.fetch_remote_branch_list", new_callable=AsyncMock) as mock_fetch_branches, ): mock_run_git_command.return_value = ( diff --git a/tests/test_notebook_utils.py b/tests/core/test_notebook_utils.py similarity index 99% rename from tests/test_notebook_utils.py rename to tests/core/test_notebook_utils.py index 6a23b92..5f4d7f0 100644 --- a/tests/test_notebook_utils.py +++ b/tests/core/test_notebook_utils.py @@ -2,7 +2,7 @@ import pytest -from gitingest.notebook_utils import process_notebook +from core.notebook_utils import process_notebook def test_process_notebook_all_cells(write_notebook): diff --git a/tests/test_query_ingestion.py b/tests/core/test_query_ingestion.py similarity index 95% rename from tests/test_query_ingestion.py rename to tests/core/test_query_ingestion.py index 0907658..89c9039 100644 --- a/tests/test_query_ingestion.py +++ b/tests/core/test_query_ingestion.py @@ -3,8 +3,8 @@ from pathlib import Path from unittest.mock import patch -from gitingest.query_ingestion import _extract_files_content, _read_file_content, _scan_directory, run_ingest_query -from gitingest.query_parser import ParsedQuery +from core.query_ingestion import _extract_files_content, _read_file_content, _scan_directory, run_ingest_query +from core.query_parser import ParsedQuery def test_scan_directory(temp_directory: Path, sample_query: ParsedQuery) -> None: @@ -44,7 +44,7 @@ def test_read_file_content_with_notebook(tmp_path: Path): notebook_path.write_text("{}", encoding="utf-8") # minimal JSON # Patch the symbol as it is used in query_ingestion - with patch("gitingest.query_ingestion.process_notebook") as mock_process: + with patch("core.query_ingestion.process_notebook") as mock_process: _read_file_content(notebook_path) mock_process.assert_called_once_with(notebook_path) @@ -53,7 +53,7 @@ def test_read_file_content_with_non_notebook(tmp_path: Path): py_file_path = tmp_path / "dummy_file.py" py_file_path.write_text("print('Hello')", encoding="utf-8") - with patch("gitingest.query_ingestion.process_notebook") as mock_process: + with patch("core.query_ingestion.process_notebook") as mock_process: _read_file_content(py_file_path) mock_process.assert_not_called() diff --git a/tests/test_repository_clone.py b/tests/core/test_repository_clone.py similarity index 84% rename from tests/test_repository_clone.py rename to tests/core/test_repository_clone.py index 9ff2736..943c8eb 100644 --- a/tests/test_repository_clone.py +++ b/tests/core/test_repository_clone.py @@ -5,8 +5,8 @@ import pytest -from gitingest.exceptions import AsyncTimeoutError -from gitingest.repository_clone import CloneConfig, _check_repo_exists, clone_repo +from core.exceptions import AsyncTimeoutError +from core.repository_clone import CloneConfig, _check_repo_exists, clone_repo @pytest.mark.asyncio @@ -22,8 +22,8 @@ async def test_clone_repo_with_commit() -> None: branch="main", ) - with patch("gitingest.repository_clone._check_repo_exists", return_value=True) as mock_check: - with patch("gitingest.repository_clone._run_git_command", new_callable=AsyncMock) as mock_exec: + with patch("core.repository_clone._check_repo_exists", return_value=True) as mock_check: + with patch("core.repository_clone._run_git_command", new_callable=AsyncMock) as mock_exec: mock_process = AsyncMock() mock_process.communicate.return_value = (b"output", b"error") mock_exec.return_value = mock_process @@ -40,8 +40,8 @@ async def test_clone_repo_without_commit() -> None: """ query = CloneConfig(url="https://github.com/user/repo", local_path="/tmp/repo", commit=None, branch="main") - with patch("gitingest.repository_clone._check_repo_exists", return_value=True) as mock_check: - with patch("gitingest.repository_clone._run_git_command", new_callable=AsyncMock) as mock_exec: + with patch("core.repository_clone._check_repo_exists", return_value=True) as mock_check: + with patch("core.repository_clone._run_git_command", new_callable=AsyncMock) as mock_exec: mock_process = AsyncMock() mock_process.communicate.return_value = (b"output", b"error") mock_exec.return_value = mock_process @@ -63,7 +63,7 @@ async def test_clone_repo_nonexistent_repository() -> None: commit=None, branch="main", ) - with patch("gitingest.repository_clone._check_repo_exists", return_value=False) as mock_check: + with patch("core.repository_clone._check_repo_exists", return_value=False) as mock_check: with pytest.raises(ValueError, match="Repository not found"): await clone_repo(clone_config) mock_check.assert_called_once_with(clone_config.url) @@ -135,8 +135,8 @@ async def test_clone_repo_with_custom_branch() -> None: local_path="/tmp/repo", branch="feature-branch", ) - with patch("gitingest.repository_clone._check_repo_exists", return_value=True): - with patch("gitingest.repository_clone._run_git_command", new_callable=AsyncMock) as mock_exec: + with patch("core.repository_clone._check_repo_exists", return_value=True): + with patch("core.repository_clone._run_git_command", new_callable=AsyncMock) as mock_exec: await clone_repo(clone_config) mock_exec.assert_called_once_with( "git", @@ -160,8 +160,8 @@ async def test_git_command_failure() -> None: url="https://github.com/user/repo", local_path="/tmp/repo", ) - with patch("gitingest.repository_clone._check_repo_exists", return_value=True): - with patch("gitingest.repository_clone._run_git_command", side_effect=RuntimeError("Git command failed")): + with patch("core.repository_clone._check_repo_exists", return_value=True): + with patch("core.repository_clone._run_git_command", side_effect=RuntimeError("Git command failed")): with pytest.raises(RuntimeError, match="Git command failed"): await clone_repo(clone_config) @@ -176,8 +176,8 @@ async def test_clone_repo_default_shallow_clone() -> None: url="https://github.com/user/repo", local_path="/tmp/repo", ) - with patch("gitingest.repository_clone._check_repo_exists", return_value=True): - with patch("gitingest.repository_clone._run_git_command", new_callable=AsyncMock) as mock_exec: + with patch("core.repository_clone._check_repo_exists", return_value=True): + with patch("core.repository_clone._run_git_command", new_callable=AsyncMock) as mock_exec: await clone_repo(clone_config) mock_exec.assert_called_once_with( "git", "clone", "--depth=1", "--single-branch", clone_config.url, clone_config.local_path @@ -195,8 +195,8 @@ async def test_clone_repo_commit_without_branch() -> None: local_path="/tmp/repo", commit="a" * 40, # Simulating a valid commit hash ) - with patch("gitingest.repository_clone._check_repo_exists", return_value=True): - with patch("gitingest.repository_clone._run_git_command", new_callable=AsyncMock) as mock_exec: + with patch("core.repository_clone._check_repo_exists", return_value=True): + with patch("core.repository_clone._run_git_command", new_callable=AsyncMock) as mock_exec: await clone_repo(clone_config) assert mock_exec.call_count == 2 # Clone and checkout calls mock_exec.assert_any_call("git", "clone", "--single-branch", clone_config.url, clone_config.local_path) @@ -245,8 +245,8 @@ async def test_clone_repo_with_timeout() -> None: """ clone_config = CloneConfig(url="https://github.com/user/repo", local_path="/tmp/repo") - with patch("gitingest.repository_clone._check_repo_exists", return_value=True): - with patch("gitingest.repository_clone._run_git_command", new_callable=AsyncMock) as mock_exec: + with patch("core.repository_clone._check_repo_exists", return_value=True): + with patch("core.repository_clone._run_git_command", new_callable=AsyncMock) as mock_exec: mock_exec.side_effect = asyncio.TimeoutError with pytest.raises(AsyncTimeoutError, match="Operation timed out after"): await clone_repo(clone_config)