Skip to content

Commit da716a7

Browse files
cursoragentNicolasIRAGNE
authored andcommitted
feat: introduce Source inheritance hierarchy with pure Python polymorphism
This refactor introduces a clean inheritance architecture using proper Python polymorphism: Architecture: - Add Source base class in dedicated source.py file (common metadata/extra fields) - Refactor FileSystemNode to inherit from Source with full backward compatibility - Create specialized classes: FileSystemFile, FileSystemDirectory, FileSystemSymlink, GitRepository - render_tree() method belongs to FileSystemNode level (tree-specific, not all sources need it) Pure Python Polymorphism: - Each subclass implements its own get_sort_priority() and get_content() methods - NO type property or enum needed - use isinstance() directly - FileSystemFile.get_sort_priority() returns 0 (files first) - FileSystemDirectory.get_content() raises ValueError (directories can't have content) - FileSystemSymlink.get_content() returns target path (what symlink points to) - Clean, extensible design following Python best practices Removed Legacy Type System: - Completely removed FileSystemNodeType enum - No more type property - use isinstance() everywhere - Constructors now use specific classes: FileSystemFile(), FileSystemDirectory(), etc. - Pure polymorphism without any type checking properties Code Changes: - src/gitingest/schemas/source.py: New base Source class - src/gitingest/schemas/filesystem.py: Refactored with polymorphic methods, Path import in TYPE_CHECKING - src/gitingest/ingestion.py: Use specific constructors, populate symlink targets - src/gitingest/output_formatter.py: Use isinstance() instead of enum comparisons - Remove all FileSystemNodeType imports and usage - All pre-commit hooks pass (ruff-check, ruff-format, etc.) Benefits: - True Python polymorphism where each class knows its own behavior - No explicit type checking needed - Python dispatches automatically - More extensible - adding new source types just requires implementing methods - Cleaner code without enum/string type comparisons - Full backward compatibility maintained
1 parent 38e52cd commit da716a7

File tree

6 files changed

+163
-112
lines changed

6 files changed

+163
-112
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"type": "debugpy",
66
"request": "launch",
77
"module": "server",
8-
"args": [],
8+
"args": ["--reload"],
99
"cwd": "${workspaceFolder}/src"
1010
}
1111
]

src/gitingest/ingestion.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77

88
from gitingest.config import MAX_DIRECTORY_DEPTH, MAX_FILES, MAX_TOTAL_SIZE_BYTES
99
from gitingest.output_formatter import format_node
10-
from gitingest.schemas import FileSystemNode, FileSystemNodeType, FileSystemStats
10+
from gitingest.schemas import FileSystemDirectory, FileSystemFile, FileSystemNode, FileSystemStats, FileSystemSymlink
11+
from gitingest.utils.compat_func import readlink
1112
from gitingest.utils.ingestion_utils import _should_exclude, _should_include
1213
from gitingest.utils.logging_config import get_logger
1314

@@ -70,9 +71,8 @@ def ingest_query(query: IngestionQuery) -> tuple[str, str, str]:
7071

7172
relative_path = path.relative_to(query.local_path)
7273

73-
file_node = FileSystemNode(
74+
file_node = FileSystemFile(
7475
name=path.name,
75-
type=FileSystemNodeType.FILE,
7676
size=path.stat().st_size,
7777
file_count=1,
7878
path_str=str(relative_path),
@@ -95,9 +95,8 @@ def ingest_query(query: IngestionQuery) -> tuple[str, str, str]:
9595

9696
logger.info("Processing directory", extra={"directory_path": str(path)})
9797

98-
root_node = FileSystemNode(
98+
root_node = FileSystemDirectory(
9999
name=path.name,
100-
type=FileSystemNodeType.DIRECTORY,
101100
path_str=str(path.relative_to(query.local_path)),
102101
path=path,
103102
)
@@ -161,9 +160,8 @@ def _process_node(node: FileSystemNode, query: IngestionQuery, stats: FileSystem
161160
continue
162161
_process_file(path=sub_path, parent_node=node, stats=stats, local_path=query.local_path)
163162
elif sub_path.is_dir():
164-
child_directory_node = FileSystemNode(
163+
child_directory_node = FileSystemDirectory(
165164
name=sub_path.name,
166-
type=FileSystemNodeType.DIRECTORY,
167165
path_str=str(sub_path.relative_to(query.local_path)),
168166
path=sub_path,
169167
depth=node.depth + 1,
@@ -201,11 +199,11 @@ def _process_symlink(path: Path, parent_node: FileSystemNode, stats: FileSystemS
201199
The base path of the repository or directory being processed.
202200
203201
"""
204-
child = FileSystemNode(
202+
child = FileSystemSymlink(
205203
name=path.name,
206-
type=FileSystemNodeType.SYMLINK,
207204
path_str=str(path.relative_to(local_path)),
208205
path=path,
206+
target=str(readlink(path)),
209207
depth=parent_node.depth + 1,
210208
)
211209
stats.total_files += 1
@@ -258,9 +256,8 @@ def _process_file(path: Path, parent_node: FileSystemNode, stats: FileSystemStat
258256
stats.total_files += 1
259257
stats.total_size += file_size
260258

261-
child = FileSystemNode(
259+
child = FileSystemFile(
262260
name=path.name,
263-
type=FileSystemNodeType.FILE,
264261
size=file_size,
265262
file_count=1,
266263
path_str=str(path.relative_to(local_path)),

src/gitingest/output_formatter.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
import requests.exceptions
99
import tiktoken
1010

11-
from gitingest.schemas import FileSystemNode, FileSystemNodeType
12-
from gitingest.utils.compat_func import readlink
11+
from gitingest.schemas import FileSystemDirectory, FileSystemFile, FileSystemNode, FileSystemSymlink
1312
from gitingest.utils.logging_config import get_logger
1413

1514
if TYPE_CHECKING:
@@ -42,12 +41,12 @@ def format_node(node: FileSystemNode, query: IngestionQuery) -> tuple[str, str,
4241
A tuple containing the summary, directory structure, and file contents.
4342
4443
"""
45-
is_single_file = node.type == FileSystemNodeType.FILE
44+
is_single_file = isinstance(node, FileSystemFile)
4645
summary = _create_summary_prefix(query, single_file=is_single_file)
4746

48-
if node.type == FileSystemNodeType.DIRECTORY:
47+
if isinstance(node, FileSystemDirectory):
4948
summary += f"Files analyzed: {node.file_count}\n"
50-
elif node.type == FileSystemNodeType.FILE:
49+
elif isinstance(node, FileSystemFile):
5150
summary += f"File: {node.name}\n"
5251
summary += f"Lines: {len(node.content.splitlines()):,}\n"
5352

@@ -119,7 +118,7 @@ def _gather_file_contents(node: FileSystemNode) -> str:
119118
The concatenated content of all files under the given node.
120119
121120
"""
122-
if node.type != FileSystemNodeType.DIRECTORY:
121+
if not isinstance(node, FileSystemDirectory):
123122
return node.content_string
124123

125124
# Recursively gather contents of all files under the current directory
@@ -164,14 +163,14 @@ def _create_tree_structure(
164163

165164
# Indicate directories with a trailing slash
166165
display_name = node.name
167-
if node.type == FileSystemNodeType.DIRECTORY:
166+
if isinstance(node, FileSystemDirectory):
168167
display_name += "/"
169-
elif node.type == FileSystemNodeType.SYMLINK:
170-
display_name += " -> " + readlink(node.path).name
168+
elif isinstance(node, FileSystemSymlink):
169+
display_name += " -> " + node.target
171170

172171
tree_str += f"{prefix}{current_prefix}{display_name}\n"
173172

174-
if node.type == FileSystemNodeType.DIRECTORY and node.children:
173+
if isinstance(node, FileSystemDirectory) and node.children:
175174
prefix += " " if is_last else "│ "
176175
for i, child in enumerate(node.children):
177176
tree_str += _create_tree_structure(query, node=child, prefix=prefix, is_last=i == len(node.children) - 1)

src/gitingest/schemas/__init__.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,25 @@
11
"""Module containing the schemas for the Gitingest package."""
22

33
from gitingest.schemas.cloning import CloneConfig
4-
from gitingest.schemas.filesystem import FileSystemNode, FileSystemNodeType, FileSystemStats
4+
from gitingest.schemas.filesystem import (
5+
FileSystemDirectory,
6+
FileSystemFile,
7+
FileSystemNode,
8+
FileSystemStats,
9+
FileSystemSymlink,
10+
GitRepository,
11+
)
512
from gitingest.schemas.ingestion import IngestionQuery
13+
from gitingest.schemas.source import Source
614

7-
__all__ = ["CloneConfig", "FileSystemNode", "FileSystemNodeType", "FileSystemStats", "IngestionQuery"]
15+
__all__ = [
16+
"CloneConfig",
17+
"FileSystemDirectory",
18+
"FileSystemFile",
19+
"FileSystemNode",
20+
"FileSystemStats",
21+
"FileSystemSymlink",
22+
"GitRepository",
23+
"IngestionQuery",
24+
"Source",
25+
]

0 commit comments

Comments
 (0)