graph-dependency-analyzer 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- graph_dependency_analyzer-0.2.0.dist-info/METADATA +158 -0
- graph_dependency_analyzer-0.2.0.dist-info/RECORD +86 -0
- graph_dependency_analyzer-0.2.0.dist-info/WHEEL +5 -0
- graph_dependency_analyzer-0.2.0.dist-info/entry_points.txt +2 -0
- graph_dependency_analyzer-0.2.0.dist-info/top_level.txt +1 -0
- src/__init__.py +0 -0
- src/application/__init__.py +0 -0
- src/application/dtos/__init__.py +0 -0
- src/application/dtos/affected_component.py +59 -0
- src/application/dtos/batch_processing_result.py +17 -0
- src/application/dtos/impact_analysis_response.py +56 -0
- src/application/dtos/impact_query_request.py +38 -0
- src/application/dtos/index_batch_request.py +53 -0
- src/application/dtos/index_batch_response.py +72 -0
- src/application/dtos/index_result.py +20 -0
- src/application/dtos/repository_index_result.py +79 -0
- src/application/exceptions.py +41 -0
- src/application/services/__init__.py +0 -0
- src/application/services/file_scanner_service.py +258 -0
- src/application/services/progress_tracker.py +132 -0
- src/application/use_cases/__init__.py +0 -0
- src/application/use_cases/clear_all_data_use_case.py +180 -0
- src/application/use_cases/index_batch_use_case.py +153 -0
- src/application/use_cases/index_repository_use_case.py +683 -0
- src/application/use_cases/indexing_orchestrator.py +181 -0
- src/application/use_cases/list_repositories_use_case.py +84 -0
- src/cli.py +512 -0
- src/config/__init__.py +5 -0
- src/config/container.py +211 -0
- src/config/logging_config.py +123 -0
- src/config/settings.py +165 -0
- src/domain/__init__.py +0 -0
- src/domain/entities/__init__.py +20 -0
- src/domain/entities/ast_node.py +32 -0
- src/domain/entities/code_chunk.py +45 -0
- src/domain/entities/code_file.py +44 -0
- src/domain/entities/dependency_graph.py +110 -0
- src/domain/entities/repository.py +46 -0
- src/domain/exceptions.py +36 -0
- src/domain/repositories/__init__.py +22 -0
- src/domain/repositories/i_code_parser.py +50 -0
- src/domain/repositories/i_dependency_extractor.py +35 -0
- src/domain/repositories/i_embedding_provider.py +31 -0
- src/domain/repositories/i_graph_repository.py +78 -0
- src/domain/repositories/i_vector_repository.py +55 -0
- src/domain/services/__init__.py +8 -0
- src/domain/services/dependency_extractor.py +962 -0
- src/domain/services/semantic_chunk_builder.py +719 -0
- src/domain/value_objects/__init__.py +12 -0
- src/domain/value_objects/chunk_type.py +24 -0
- src/domain/value_objects/dependency_type.py +22 -0
- src/domain/value_objects/node_type.py +22 -0
- src/domain/value_objects/qualified_name.py +78 -0
- src/infrastructure/__init__.py +0 -0
- src/infrastructure/exceptions.py +41 -0
- src/infrastructure/external_apis/__init__.py +0 -0
- src/infrastructure/external_apis/ollama_embedding_provider.py +134 -0
- src/infrastructure/external_apis/openai_embedding_provider.py +231 -0
- src/infrastructure/parsing/__init__.py +20 -0
- src/infrastructure/parsing/config_file_parser.py +347 -0
- src/infrastructure/parsing/gradle_dependency_parser.py +159 -0
- src/infrastructure/parsing/groovy_parser.py +198 -0
- src/infrastructure/parsing/maven_dependency_parser.py +147 -0
- src/infrastructure/parsing/microfrontend_config_parser.py +242 -0
- src/infrastructure/parsing/npm_package_dependency_parser.py +255 -0
- src/infrastructure/parsing/python_parser.py +211 -0
- src/infrastructure/parsing/sql_schema_parser.py +658 -0
- src/infrastructure/parsing/tree_sitter_java_parser.py +434 -0
- src/infrastructure/parsing/tree_sitter_typescript_parser.py +600 -0
- src/infrastructure/persistence/__init__.py +0 -0
- src/infrastructure/persistence/graph_export_repository.py +443 -0
- src/infrastructure/persistence/neo4j_dependency_repository.py +657 -0
- src/infrastructure/persistence/qdrant_vector_repository.py +357 -0
- src/infrastructure/persistence/repository_relationship_repository.py +1312 -0
- src/presentation/__init__.py +0 -0
- src/presentation/api/__init__.py +0 -0
- src/presentation/api/rest/__init__.py +0 -0
- src/presentation/api/rest/exception_handlers.py +174 -0
- src/presentation/api/rest/main.py +80 -0
- src/presentation/api/rest/routers/__init__.py +5 -0
- src/presentation/api/rest/routers/admin.py +103 -0
- src/presentation/api/rest/routers/analysis.py +980 -0
- src/presentation/api/rest/routers/export.py +442 -0
- src/presentation/api/rest/routers/health.py +117 -0
- src/presentation/api/rest/routers/indexing.py +148 -0
- src/presentation/api/rest/routers/relationships.py +1089 -0
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI Exception Handlers (Task 9.3)
|
|
3
|
+
|
|
4
|
+
Global exception handlers for the REST API.
|
|
5
|
+
"""
|
|
6
|
+
from fastapi import Request, status
|
|
7
|
+
from fastapi.responses import JSONResponse
|
|
8
|
+
from fastapi.exceptions import RequestValidationError
|
|
9
|
+
import structlog
|
|
10
|
+
|
|
11
|
+
from src.application.exceptions import (
|
|
12
|
+
ApplicationException,
|
|
13
|
+
EmptyRepositoryListError,
|
|
14
|
+
NoValidRepositoriesError,
|
|
15
|
+
RepositoryNotFoundError
|
|
16
|
+
)
|
|
17
|
+
from src.infrastructure.exceptions import (
|
|
18
|
+
InfrastructureException,
|
|
19
|
+
OpenAIAPIError,
|
|
20
|
+
QdrantConnectionError,
|
|
21
|
+
Neo4jConnectionError
|
|
22
|
+
)
|
|
23
|
+
from src.domain.exceptions import DomainException
|
|
24
|
+
|
|
25
|
+
logger = structlog.get_logger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def application_exception_handler(
|
|
29
|
+
request: Request,
|
|
30
|
+
exc: ApplicationException
|
|
31
|
+
) -> JSONResponse:
|
|
32
|
+
"""
|
|
33
|
+
Handle application layer exceptions.
|
|
34
|
+
|
|
35
|
+
Returns HTTP 400 for user errors.
|
|
36
|
+
"""
|
|
37
|
+
logger.warning(
|
|
38
|
+
"application_error",
|
|
39
|
+
error_type=type(exc).__name__,
|
|
40
|
+
message=exc.message,
|
|
41
|
+
details=exc.details,
|
|
42
|
+
path=request.url.path
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return JSONResponse(
|
|
46
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
47
|
+
content={
|
|
48
|
+
"error": type(exc).__name__,
|
|
49
|
+
"message": exc.message,
|
|
50
|
+
"details": exc.details
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def infrastructure_exception_handler(
|
|
56
|
+
request: Request,
|
|
57
|
+
exc: InfrastructureException
|
|
58
|
+
) -> JSONResponse:
|
|
59
|
+
"""
|
|
60
|
+
Handle infrastructure layer exceptions.
|
|
61
|
+
|
|
62
|
+
Returns HTTP 500 for system errors.
|
|
63
|
+
Logs full details but returns generic message to client.
|
|
64
|
+
"""
|
|
65
|
+
logger.error(
|
|
66
|
+
"infrastructure_error",
|
|
67
|
+
error_type=type(exc).__name__,
|
|
68
|
+
message=exc.message,
|
|
69
|
+
details=exc.details,
|
|
70
|
+
path=request.url.path,
|
|
71
|
+
exc_info=True
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return JSONResponse(
|
|
75
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
76
|
+
content={
|
|
77
|
+
"error": "InternalServerError",
|
|
78
|
+
"message": "An internal error occurred. Please try again later."
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
async def domain_exception_handler(
|
|
84
|
+
request: Request,
|
|
85
|
+
exc: DomainException
|
|
86
|
+
) -> JSONResponse:
|
|
87
|
+
"""
|
|
88
|
+
Handle domain layer exceptions.
|
|
89
|
+
|
|
90
|
+
Returns HTTP 400 for domain rule violations.
|
|
91
|
+
"""
|
|
92
|
+
logger.warning(
|
|
93
|
+
"domain_error",
|
|
94
|
+
error_type=type(exc).__name__,
|
|
95
|
+
message=exc.message,
|
|
96
|
+
details=exc.details,
|
|
97
|
+
path=request.url.path
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return JSONResponse(
|
|
101
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
102
|
+
content={
|
|
103
|
+
"error": type(exc).__name__,
|
|
104
|
+
"message": exc.message,
|
|
105
|
+
"details": exc.details
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
async def validation_exception_handler(
|
|
111
|
+
request: Request,
|
|
112
|
+
exc: RequestValidationError
|
|
113
|
+
) -> JSONResponse:
|
|
114
|
+
"""
|
|
115
|
+
Handle Pydantic validation errors.
|
|
116
|
+
|
|
117
|
+
Returns HTTP 422 for validation failures.
|
|
118
|
+
"""
|
|
119
|
+
logger.warning(
|
|
120
|
+
"validation_error",
|
|
121
|
+
errors=exc.errors(),
|
|
122
|
+
body=exc.body,
|
|
123
|
+
path=request.url.path
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return JSONResponse(
|
|
127
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
128
|
+
content={
|
|
129
|
+
"error": "ValidationError",
|
|
130
|
+
"message": "Request validation failed",
|
|
131
|
+
"details": exc.errors()
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
async def unhandled_exception_handler(
|
|
137
|
+
request: Request,
|
|
138
|
+
exc: Exception
|
|
139
|
+
) -> JSONResponse:
|
|
140
|
+
"""
|
|
141
|
+
Handle any unhandled exceptions.
|
|
142
|
+
|
|
143
|
+
Returns HTTP 500 with generic message.
|
|
144
|
+
Logs full stack trace.
|
|
145
|
+
"""
|
|
146
|
+
logger.error(
|
|
147
|
+
"unhandled_error",
|
|
148
|
+
error_type=type(exc).__name__,
|
|
149
|
+
message=str(exc),
|
|
150
|
+
path=request.url.path,
|
|
151
|
+
exc_info=True
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
return JSONResponse(
|
|
155
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
156
|
+
content={
|
|
157
|
+
"error": "InternalServerError",
|
|
158
|
+
"message": "An unexpected error occurred. Please contact support."
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def register_exception_handlers(app):
|
|
164
|
+
"""
|
|
165
|
+
Register all exception handlers with FastAPI app.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
app: FastAPI application instance
|
|
169
|
+
"""
|
|
170
|
+
app.add_exception_handler(ApplicationException, application_exception_handler)
|
|
171
|
+
app.add_exception_handler(InfrastructureException, infrastructure_exception_handler)
|
|
172
|
+
app.add_exception_handler(DomainException, domain_exception_handler)
|
|
173
|
+
app.add_exception_handler(RequestValidationError, validation_exception_handler)
|
|
174
|
+
app.add_exception_handler(Exception, unhandled_exception_handler)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI Application (Task 9.2)
|
|
3
|
+
|
|
4
|
+
Main FastAPI application with all routers and middleware.
|
|
5
|
+
"""
|
|
6
|
+
from fastapi import FastAPI
|
|
7
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
8
|
+
import structlog
|
|
9
|
+
|
|
10
|
+
from src.config.container import get_container
|
|
11
|
+
from src.presentation.api.rest.routers import health, indexing, analysis, admin, export, relationships
|
|
12
|
+
from src.presentation.api.rest.exception_handlers import register_exception_handlers
|
|
13
|
+
|
|
14
|
+
logger = structlog.get_logger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def create_app() -> FastAPI:
|
|
18
|
+
"""
|
|
19
|
+
Create and configure FastAPI application.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Configured FastAPI instance with all routers and middleware
|
|
23
|
+
"""
|
|
24
|
+
app = FastAPI(
|
|
25
|
+
title="Knowledge Graph Indexing API",
|
|
26
|
+
description="API for indexing Java repositories and performing impact analysis",
|
|
27
|
+
version="1.0.0",
|
|
28
|
+
docs_url="/docs",
|
|
29
|
+
redoc_url="/redoc"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Configure CORS
|
|
33
|
+
app.add_middleware(
|
|
34
|
+
CORSMiddleware,
|
|
35
|
+
allow_origins=["*"], # Configure appropriately for production
|
|
36
|
+
allow_credentials=True,
|
|
37
|
+
allow_methods=["*"],
|
|
38
|
+
allow_headers=["*"],
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Configure dependency injection
|
|
42
|
+
container = get_container()
|
|
43
|
+
container.wire(modules=[
|
|
44
|
+
"src.presentation.api.rest.routers.indexing",
|
|
45
|
+
"src.presentation.api.rest.routers.analysis",
|
|
46
|
+
"src.presentation.api.rest.routers.admin",
|
|
47
|
+
"src.presentation.api.rest.routers.export",
|
|
48
|
+
"src.presentation.api.rest.routers.relationships",
|
|
49
|
+
])
|
|
50
|
+
|
|
51
|
+
# Register exception handlers
|
|
52
|
+
register_exception_handlers(app)
|
|
53
|
+
|
|
54
|
+
# Register routers
|
|
55
|
+
app.include_router(health.router)
|
|
56
|
+
app.include_router(indexing.router)
|
|
57
|
+
app.include_router(analysis.router)
|
|
58
|
+
app.include_router(admin.router)
|
|
59
|
+
app.include_router(export.router)
|
|
60
|
+
app.include_router(relationships.router)
|
|
61
|
+
|
|
62
|
+
logger.info("fastapi_application_created", container_wired=True)
|
|
63
|
+
|
|
64
|
+
return app
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Create application instance
|
|
68
|
+
app = create_app()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@app.on_event("startup")
|
|
72
|
+
async def startup_event():
|
|
73
|
+
"""Application startup event handler."""
|
|
74
|
+
logger.info("application_starting")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@app.on_event("shutdown")
|
|
78
|
+
async def shutdown_event():
|
|
79
|
+
"""Application shutdown event handler."""
|
|
80
|
+
logger.info("application_shutting_down")
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Admin Router
|
|
3
|
+
|
|
4
|
+
REST API endpoints for administrative operations.
|
|
5
|
+
|
|
6
|
+
⚠️ WARNING: These endpoints perform DESTRUCTIVE operations!
|
|
7
|
+
Use with caution in production environments.
|
|
8
|
+
"""
|
|
9
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
import structlog
|
|
12
|
+
|
|
13
|
+
from dependency_injector.wiring import inject, Provide
|
|
14
|
+
|
|
15
|
+
from src.application.use_cases.clear_all_data_use_case import ClearAllDataUseCase
|
|
16
|
+
from src.config.container import Container
|
|
17
|
+
|
|
18
|
+
logger = structlog.get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
router = APIRouter(prefix="/admin", tags=["admin"])
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ClearAllResponse(BaseModel):
|
|
24
|
+
"""Response model for clear all operation."""
|
|
25
|
+
message: str
|
|
26
|
+
neo4j: dict
|
|
27
|
+
qdrant: dict
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@router.delete(
|
|
31
|
+
"/clear-all",
|
|
32
|
+
response_model=ClearAllResponse,
|
|
33
|
+
status_code=status.HTTP_200_OK,
|
|
34
|
+
responses={
|
|
35
|
+
200: {"description": "All data cleared successfully"},
|
|
36
|
+
500: {"description": "Internal server error"}
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
@inject
|
|
40
|
+
async def clear_all_data(
|
|
41
|
+
use_case: ClearAllDataUseCase = Depends(Provide[Container.clear_all_data_use_case])
|
|
42
|
+
) -> ClearAllResponse:
|
|
43
|
+
"""
|
|
44
|
+
Clear all data from Qdrant and Neo4j.
|
|
45
|
+
|
|
46
|
+
⚠️ **DESTRUCTIVE OPERATION** - Deletes:
|
|
47
|
+
- All nodes and relationships in Neo4j
|
|
48
|
+
- All vectors in Qdrant collection
|
|
49
|
+
|
|
50
|
+
This operation is **irreversible**. All indexed repositories will be lost.
|
|
51
|
+
|
|
52
|
+
Use case:
|
|
53
|
+
- Clean environment before re-indexing
|
|
54
|
+
- Reset system to initial state
|
|
55
|
+
- Testing and development
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
ClearAllResponse with deletion metrics
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
HTTP 500: Internal error during deletion
|
|
62
|
+
"""
|
|
63
|
+
logger.warning("clear_all_requested", operation="DESTRUCTIVE")
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
results = await use_case.execute()
|
|
67
|
+
|
|
68
|
+
# Check if both operations succeeded
|
|
69
|
+
neo4j_success = results["neo4j"]["success"]
|
|
70
|
+
qdrant_success = results["qdrant"]["success"]
|
|
71
|
+
|
|
72
|
+
if neo4j_success and qdrant_success:
|
|
73
|
+
message = "All data cleared successfully"
|
|
74
|
+
elif neo4j_success:
|
|
75
|
+
message = "Neo4j cleared, but Qdrant failed"
|
|
76
|
+
elif qdrant_success:
|
|
77
|
+
message = "Qdrant cleared, but Neo4j failed"
|
|
78
|
+
else:
|
|
79
|
+
message = "Both operations failed"
|
|
80
|
+
|
|
81
|
+
logger.warning(
|
|
82
|
+
"clear_all_completed",
|
|
83
|
+
message=message,
|
|
84
|
+
neo4j_success=neo4j_success,
|
|
85
|
+
qdrant_success=qdrant_success
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return ClearAllResponse(
|
|
89
|
+
message=message,
|
|
90
|
+
neo4j=results["neo4j"],
|
|
91
|
+
qdrant=results["qdrant"]
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.error(
|
|
96
|
+
"clear_all_failed",
|
|
97
|
+
error=str(e),
|
|
98
|
+
error_type=type(e).__name__
|
|
99
|
+
)
|
|
100
|
+
raise HTTPException(
|
|
101
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
102
|
+
detail=f"Failed to clear data: {str(e)}"
|
|
103
|
+
)
|