codegraphcontext 0.1.9__tar.gz → 0.1.10__tar.gz
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.
- {codegraphcontext-0.1.9/src/codegraphcontext.egg-info → codegraphcontext-0.1.10}/PKG-INFO +12 -3
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/README.md +11 -2
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/pyproject.toml +1 -1
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext/cli/main.py +77 -35
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext/core/database.py +30 -7
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext/core/jobs.py +24 -13
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext/core/watcher.py +53 -25
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext/prompts.py +7 -1
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext/server.py +102 -30
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext/tools/graph_builder.py +1 -1
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext/tools/import_extractor.py +44 -17
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10/src/codegraphcontext.egg-info}/PKG-INFO +12 -3
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/LICENSE +0 -0
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/setup.cfg +0 -0
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext/__init__.py +0 -0
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext/__main__.py +0 -0
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext/cli/__init__.py +0 -0
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext/cli/setup_wizard.py +0 -0
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext/core/__init__.py +0 -0
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext/tools/__init__.py +0 -0
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext/tools/code_finder.py +0 -0
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext/tools/system.py +0 -0
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext.egg-info/SOURCES.txt +0 -0
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext.egg-info/dependency_links.txt +0 -0
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext.egg-info/entry_points.txt +0 -0
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext.egg-info/requires.txt +0 -0
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/src/codegraphcontext.egg-info/top_level.txt +0 -0
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/tests/test_cgc_integration.py +0 -0
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/tests/test_imports.py +0 -0
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/tests/test_jsonrpc.py +0 -0
- {codegraphcontext-0.1.9 → codegraphcontext-0.1.10}/tests/test_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codegraphcontext
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
4
4
|
Summary: An MCP server that indexes local code into a graph database to provide context to AI assistants.
|
|
5
5
|
Author-email: Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -60,9 +60,10 @@ Dynamic: license-file
|
|
|
60
60
|
An MCP server that indexes local code into a graph database to provide context to AI assistants.
|
|
61
61
|
|
|
62
62
|
## Project Details
|
|
63
|
-
- **Version:** 0.1.
|
|
63
|
+
- **Version:** 0.1.10
|
|
64
64
|
- **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
|
|
65
65
|
- **License:** MIT License (See [LICENSE](LICENSE) for details)
|
|
66
|
+
- **Website:** [CodeGraphContext](http://code-graph-context.vercel.app/)
|
|
66
67
|
|
|
67
68
|
## Features
|
|
68
69
|
|
|
@@ -96,8 +97,15 @@ If you’re using CodeGraphContext in your project, feel free to open a PR and a
|
|
|
96
97
|
|
|
97
98
|
1. **Install:** `pip install codegraphcontext`
|
|
98
99
|
2. **Setup:** `cgc setup`
|
|
100
|
+
This interactive command guides you through configuring your Neo4j database connection. It offers several options:
|
|
101
|
+
* **Local Setup (Docker Recommended):** Helps you set up a local Neo4j instance using Docker, which is the easiest way to get started.
|
|
102
|
+
* **Local Setup (Linux Binary):** For Debian-based Linux systems (like Ubuntu), `cgc setup` can automate the installation of Neo4j directly on your machine.
|
|
103
|
+
* **Hosted Setup:** Allows you to connect to an existing remote Neo4j database (e.g., Neo4j AuraDB).
|
|
104
|
+
Upon successful configuration, `cgc setup` will generate two important files:
|
|
105
|
+
* `mcp.json`: Contains the MCP client configuration for CodeGraphContext.
|
|
106
|
+
* `~/.codegraphcontext/.env`: Stores your Neo4j connection credentials securely.
|
|
99
107
|
3. **Start:** `cgc start`
|
|
100
|
-
|
|
108
|
+
|
|
101
109
|
|
|
102
110
|
## MCP Client Configuration
|
|
103
111
|
|
|
@@ -205,6 +213,7 @@ Once the server is running, you can interact with it through your AI assistant u
|
|
|
205
213
|
## Contributing
|
|
206
214
|
|
|
207
215
|
Contributions are welcome! 🎉
|
|
216
|
+
Please see our [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
|
|
208
217
|
If you have ideas for new features, integrations, or improvements, open an [issue](https://github.com/Shashankss1205/CodeGraphContext/issues) or submit a PR.
|
|
209
218
|
|
|
210
219
|
Join discussions and help shape the future of CodeGraphContext.
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
An MCP server that indexes local code into a graph database to provide context to AI assistants.
|
|
9
9
|
|
|
10
10
|
## Project Details
|
|
11
|
-
- **Version:** 0.1.
|
|
11
|
+
- **Version:** 0.1.10
|
|
12
12
|
- **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
|
|
13
13
|
- **License:** MIT License (See [LICENSE](LICENSE) for details)
|
|
14
|
+
- **Website:** [CodeGraphContext](http://code-graph-context.vercel.app/)
|
|
14
15
|
|
|
15
16
|
## Features
|
|
16
17
|
|
|
@@ -44,8 +45,15 @@ If you’re using CodeGraphContext in your project, feel free to open a PR and a
|
|
|
44
45
|
|
|
45
46
|
1. **Install:** `pip install codegraphcontext`
|
|
46
47
|
2. **Setup:** `cgc setup`
|
|
48
|
+
This interactive command guides you through configuring your Neo4j database connection. It offers several options:
|
|
49
|
+
* **Local Setup (Docker Recommended):** Helps you set up a local Neo4j instance using Docker, which is the easiest way to get started.
|
|
50
|
+
* **Local Setup (Linux Binary):** For Debian-based Linux systems (like Ubuntu), `cgc setup` can automate the installation of Neo4j directly on your machine.
|
|
51
|
+
* **Hosted Setup:** Allows you to connect to an existing remote Neo4j database (e.g., Neo4j AuraDB).
|
|
52
|
+
Upon successful configuration, `cgc setup` will generate two important files:
|
|
53
|
+
* `mcp.json`: Contains the MCP client configuration for CodeGraphContext.
|
|
54
|
+
* `~/.codegraphcontext/.env`: Stores your Neo4j connection credentials securely.
|
|
47
55
|
3. **Start:** `cgc start`
|
|
48
|
-
|
|
56
|
+
|
|
49
57
|
|
|
50
58
|
## MCP Client Configuration
|
|
51
59
|
|
|
@@ -153,6 +161,7 @@ Once the server is running, you can interact with it through your AI assistant u
|
|
|
153
161
|
## Contributing
|
|
154
162
|
|
|
155
163
|
Contributions are welcome! 🎉
|
|
164
|
+
Please see our [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
|
|
156
165
|
If you have ideas for new features, integrations, or improvements, open an [issue](https://github.com/Shashankss1205/CodeGraphContext/issues) or submit a PR.
|
|
157
166
|
|
|
158
167
|
Join discussions and help shape the future of CodeGraphContext.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codegraphcontext"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.10"
|
|
4
4
|
description = "An MCP server that indexes local code into a graph database to provide context to AI assistants."
|
|
5
5
|
authors = [{ name = "Shashank Shekhar Singh", email = "shashankshekharsingh1205@gmail.com" }]
|
|
6
6
|
readme = "README.md"
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
# src/codegraphcontext/cli/main.py
|
|
2
|
+
"""
|
|
3
|
+
This module defines the command-line interface (CLI) for the CodeGraphContext application.
|
|
4
|
+
It uses the Typer library to create a user-friendly and well-documented CLI.
|
|
5
|
+
|
|
6
|
+
Commands:
|
|
7
|
+
- setup: Runs an interactive wizard to configure the Neo4j database connection.
|
|
8
|
+
- start: Launches the main MCP server.
|
|
9
|
+
- tool: A placeholder for directly calling server tools (for debugging).
|
|
10
|
+
- help: Displays help information.
|
|
11
|
+
- version: Show the installed version.
|
|
12
|
+
"""
|
|
2
13
|
import typer
|
|
3
14
|
from rich.console import Console
|
|
4
15
|
import asyncio
|
|
@@ -7,12 +18,14 @@ import json
|
|
|
7
18
|
import os
|
|
8
19
|
from pathlib import Path
|
|
9
20
|
from dotenv import load_dotenv, find_dotenv
|
|
21
|
+
from importlib.metadata import version as pkg_version, PackageNotFoundError
|
|
10
22
|
from codegraphcontext.server import MCPServer
|
|
11
23
|
from .setup_wizard import run_setup_wizard
|
|
12
24
|
|
|
13
|
-
# Set the log level for the noisy neo4j logger to WARNING
|
|
14
|
-
logging.getLogger("neo4j").setLevel(logging.WARNING)
|
|
25
|
+
# Set the log level for the noisy neo4j logger to WARNING to keep the output clean.
|
|
26
|
+
logging.getLogger("neo4j").setLevel(logging.WARNING)
|
|
15
27
|
|
|
28
|
+
# Initialize the Typer app and Rich console for formatted output.
|
|
16
29
|
app = typer.Typer(
|
|
17
30
|
name="cgc",
|
|
18
31
|
help="CodeGraphContext: An MCP server for AI-powered code analysis.",
|
|
@@ -20,29 +33,49 @@ app = typer.Typer(
|
|
|
20
33
|
)
|
|
21
34
|
console = Console()
|
|
22
35
|
|
|
36
|
+
# Configure basic logging for the application.
|
|
23
37
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
|
24
38
|
|
|
39
|
+
|
|
40
|
+
def get_version() -> str:
|
|
41
|
+
"""
|
|
42
|
+
Try to read version from the installed package metadata.
|
|
43
|
+
Fallback to a dev version if not installed.
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
return pkg_version("codegraphcontext") # must match [project].name in pyproject.toml
|
|
47
|
+
except PackageNotFoundError:
|
|
48
|
+
return "0.0.0 (dev)"
|
|
49
|
+
|
|
50
|
+
|
|
25
51
|
@app.command()
|
|
26
52
|
def setup():
|
|
27
53
|
"""
|
|
28
|
-
|
|
54
|
+
Runs the interactive setup wizard to configure the server and database connection.
|
|
55
|
+
This helps users set up a local Docker-based Neo4j instance or connect to a remote one.
|
|
29
56
|
"""
|
|
30
57
|
run_setup_wizard()
|
|
31
58
|
|
|
32
59
|
@app.command()
|
|
33
60
|
def start():
|
|
34
61
|
"""
|
|
35
|
-
|
|
62
|
+
Starts the CodeGraphContext MCP server, which listens for JSON-RPC requests from stdin.
|
|
63
|
+
It first attempts to load Neo4j credentials from various sources before starting.
|
|
36
64
|
"""
|
|
37
65
|
console.print("[bold green]Starting CodeGraphContext Server...[/bold green]")
|
|
38
|
-
|
|
39
|
-
#
|
|
66
|
+
|
|
67
|
+
# The server needs Neo4j credentials. It attempts to load them in the following order of priority:
|
|
68
|
+
# 1. From a local `mcp.json` file in the current working directory.
|
|
69
|
+
# 2. From a global `.env` file at `~/.codegraphcontext/.env`.
|
|
70
|
+
# 3. From any `.env` file found by searching upwards from the current directory.
|
|
71
|
+
|
|
72
|
+
# 1. Prefer loading environment variables from mcp.json in the current directory.
|
|
40
73
|
mcp_file_path = Path.cwd() / "mcp.json"
|
|
41
74
|
if mcp_file_path.exists():
|
|
42
75
|
try:
|
|
43
76
|
with open(mcp_file_path, "r") as f:
|
|
44
77
|
mcp_config = json.load(f)
|
|
45
|
-
|
|
78
|
+
|
|
46
79
|
server_env = mcp_config.get("mcpServers", {}).get("CodeGraphContext", {}).get("env", {})
|
|
47
80
|
for key, value in server_env.items():
|
|
48
81
|
os.environ[key] = value
|
|
@@ -51,7 +84,7 @@ def start():
|
|
|
51
84
|
console.print(f"[bold red]Error loading mcp.json:[/bold red] {e}")
|
|
52
85
|
console.print("[yellow]Attempting to start server without mcp.json environment variables.[/yellow]")
|
|
53
86
|
else:
|
|
54
|
-
# 2. If no local mcp.json, try to load from
|
|
87
|
+
# 2. If no local mcp.json, try to load from the global config directory.
|
|
55
88
|
global_env_path = Path.home() / ".codegraphcontext" / ".env"
|
|
56
89
|
if global_env_path.exists():
|
|
57
90
|
try:
|
|
@@ -61,30 +94,34 @@ def start():
|
|
|
61
94
|
console.print(f"[bold red]Error loading global .env file from {global_env_path}:[/bold red] {e}")
|
|
62
95
|
console.print("[yellow]Attempting to start server without .env environment variables.[/yellow]")
|
|
63
96
|
else:
|
|
64
|
-
# Fallback: try to load from
|
|
97
|
+
# 3. Fallback: try to load from any .env file found by searching up the directory tree.
|
|
65
98
|
try:
|
|
66
99
|
dotenv_path = find_dotenv(usecwd=True, raise_error_if_not_found=False)
|
|
67
100
|
if dotenv_path:
|
|
68
101
|
load_dotenv(dotenv_path)
|
|
69
|
-
console.print(f"[green]Loaded Neo4j credentials from
|
|
102
|
+
console.print(f"[green]Loaded Neo4j credentials from discovered .env file: {dotenv_path}[/green]")
|
|
70
103
|
else:
|
|
71
104
|
console.print("[yellow]No local mcp.json or global .env file found. Attempting to start server without explicit Neo4j credentials.[/yellow]")
|
|
72
105
|
except Exception as e:
|
|
73
|
-
console.print(f"[bold red]Error loading
|
|
106
|
+
console.print(f"[bold red]Error loading .env file:[/bold red] {e}")
|
|
74
107
|
console.print("[yellow]Attempting to start server without .env environment variables.[/yellow]")
|
|
75
108
|
|
|
76
109
|
server = None
|
|
77
110
|
loop = asyncio.new_event_loop()
|
|
78
111
|
asyncio.set_event_loop(loop)
|
|
79
112
|
try:
|
|
113
|
+
# Initialize and run the main server.
|
|
80
114
|
server = MCPServer(loop=loop)
|
|
81
115
|
loop.run_until_complete(server.run())
|
|
82
116
|
except ValueError as e:
|
|
117
|
+
# This typically happens if credentials are still not found after all checks.
|
|
83
118
|
console.print(f"[bold red]Configuration Error:[/bold red] {e}")
|
|
84
119
|
console.print("Please run `cgc setup` to configure the server.")
|
|
85
120
|
except KeyboardInterrupt:
|
|
121
|
+
# Handle graceful shutdown on Ctrl+C.
|
|
86
122
|
console.print("\n[bold yellow]Server stopped by user.[/bold yellow]")
|
|
87
123
|
finally:
|
|
124
|
+
# Ensure server and event loop are properly closed.
|
|
88
125
|
if server:
|
|
89
126
|
server.shutdown()
|
|
90
127
|
loop.close()
|
|
@@ -96,46 +133,51 @@ def tool(
|
|
|
96
133
|
args: str = typer.Argument("{}", help="A JSON string of arguments for the tool."),
|
|
97
134
|
):
|
|
98
135
|
"""
|
|
99
|
-
Directly call a CodeGraphContext tool.
|
|
100
|
-
Note: This command instantiates a new, independent MCP server instance for each call.
|
|
101
|
-
Therefore, it does not share state (like job IDs) with a server started via `cgc start`.
|
|
102
|
-
|
|
103
|
-
This command can be used for:\n
|
|
104
|
-
- `add_code_to_graph`: Index a new project or directory. Args: `path` (str), `is_dependency` (bool, optional)\n
|
|
105
|
-
- `add_package_to_graph`: Add a Python package to the graph. Args: `package_name` (str), `is_dependency` (bool, optional)\n
|
|
106
|
-
- `find_code`: Search for code snippets. Args: `query` (str)\n
|
|
107
|
-
- `analyze_code_relationships`: Analyze code relationships (e.g., callers, callees). Args: `query_type` (str), `target` (str), `context` (str, optional)\n
|
|
108
|
-
- `watch_directory`: Start watching a directory for changes. Args: `path` (str)\n
|
|
109
|
-
- `execute_cypher_query`: Run direct Cypher queries. Args: `cypher_query` (str)\n
|
|
110
|
-
- `list_imports`: List imports from files. Args: `path` (str), `language` (str, optional), `recursive` (bool, optional)\n
|
|
111
|
-
- `find_dead_code`: Find potentially unused functions. Args: None\n
|
|
112
|
-
- `calculate_cyclomatic_complexity`: Calculate function complexity. Args: `function_name` (str), `file_path` (str, optional)\n
|
|
113
|
-
- `find_most_complex_functions`: Find the most complex functions. Args: `limit` (int, optional)\n
|
|
114
|
-
- `list_indexed_repositories`: List indexed repositories. Args: None\n
|
|
115
|
-
- `delete_repository`: Delete an indexed repository. Args: `repo_path` (str)
|
|
116
|
-
"""
|
|
136
|
+
Directly call a CodeGraphContext tool from the command line.
|
|
117
137
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
138
|
+
IMPORTANT: This is a placeholder for debugging and does not connect to a running
|
|
139
|
+
server. It creates a new, temporary server instance for each call, so it cannot
|
|
140
|
+
be used to check the status of jobs started by `cgc start`.
|
|
141
|
+
"""
|
|
121
142
|
console.print(f"Calling tool [bold cyan]{name}[/bold cyan] with args: {args}")
|
|
122
143
|
console.print("[yellow]Note: This is a placeholder for direct tool invocation.[/yellow]")
|
|
123
144
|
|
|
124
145
|
@app.command()
|
|
125
146
|
def help(ctx: typer.Context):
|
|
126
|
-
"""Show
|
|
147
|
+
"""Show the main help message and exit."""
|
|
127
148
|
root_ctx = ctx.parent or ctx
|
|
128
149
|
typer.echo(root_ctx.get_help())
|
|
129
150
|
|
|
130
151
|
|
|
152
|
+
@app.command("version")
|
|
153
|
+
def version_cmd():
|
|
154
|
+
"""Show the application version."""
|
|
155
|
+
console.print(f"CodeGraphContext [bold cyan]{get_version()}[/bold cyan]")
|
|
156
|
+
|
|
157
|
+
|
|
131
158
|
@app.callback(invoke_without_command=True)
|
|
132
|
-
def main(
|
|
159
|
+
def main(
|
|
160
|
+
ctx: typer.Context,
|
|
161
|
+
version_: bool = typer.Option(
|
|
162
|
+
None,
|
|
163
|
+
"--version",
|
|
164
|
+
"-v",
|
|
165
|
+
help="Show the application version and exit.",
|
|
166
|
+
is_eager=True,
|
|
167
|
+
),
|
|
168
|
+
):
|
|
133
169
|
"""
|
|
134
|
-
|
|
170
|
+
Main entry point for the cgc CLI application.
|
|
171
|
+
If no subcommand is provided, it displays a welcome message with instructions.
|
|
135
172
|
"""
|
|
173
|
+
if version_:
|
|
174
|
+
console.print(f"CodeGraphContext [bold cyan]{get_version()}[/bold cyan]")
|
|
175
|
+
raise typer.Exit()
|
|
176
|
+
|
|
136
177
|
if ctx.invoked_subcommand is None:
|
|
137
178
|
console.print("[bold green]👋 Welcome to CodeGraphContext (cgc)![/bold green]\n")
|
|
138
179
|
console.print("👉 Run [cyan]cgc setup[/cyan] to configure the server and database.")
|
|
139
180
|
console.print("👉 Run [cyan]cgc start[/cyan] to launch the server.")
|
|
140
181
|
console.print("👉 Run [cyan]cgc help[/cyan] to see all available commands.\n")
|
|
182
|
+
console.print("👉 Run [cyan]cgc --version[/cyan] to check the version.\n")
|
|
141
183
|
console.print("👉 Running [green]codegraphcontext [white]works the same as using [green]cgc")
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
# src/codegraphcontext/core/database.py
|
|
2
|
+
"""
|
|
3
|
+
This module provides a thread-safe singleton manager for the Neo4j database connection.
|
|
4
|
+
"""
|
|
2
5
|
import os
|
|
3
6
|
import logging
|
|
4
7
|
import threading
|
|
@@ -10,20 +13,30 @@ logger = logging.getLogger(__name__)
|
|
|
10
13
|
|
|
11
14
|
class DatabaseManager:
|
|
12
15
|
"""
|
|
13
|
-
|
|
16
|
+
Manages the Neo4j database driver as a singleton to ensure only one
|
|
17
|
+
connection pool is created and shared across the application.
|
|
18
|
+
|
|
19
|
+
This pattern is crucial for performance and resource management in a
|
|
20
|
+
multi-threaded or asynchronous application.
|
|
14
21
|
"""
|
|
15
22
|
_instance = None
|
|
16
23
|
_driver: Optional[Driver] = None
|
|
17
|
-
_lock = threading.Lock()
|
|
24
|
+
_lock = threading.Lock() # Lock to ensure thread-safe initialization.
|
|
18
25
|
|
|
19
26
|
def __new__(cls):
|
|
27
|
+
"""Standard singleton pattern implementation."""
|
|
20
28
|
if cls._instance is None:
|
|
21
29
|
with cls._lock:
|
|
30
|
+
# Double-check locking to prevent race conditions.
|
|
22
31
|
if cls._instance is None:
|
|
23
32
|
cls._instance = super(DatabaseManager, cls).__new__(cls)
|
|
24
33
|
return cls._instance
|
|
25
34
|
|
|
26
35
|
def __init__(self):
|
|
36
|
+
"""
|
|
37
|
+
Initializes the manager by reading credentials from environment variables.
|
|
38
|
+
The `_initialized` flag prevents re-initialization on subsequent calls.
|
|
39
|
+
"""
|
|
27
40
|
if hasattr(self, '_initialized'):
|
|
28
41
|
return
|
|
29
42
|
|
|
@@ -33,10 +46,20 @@ class DatabaseManager:
|
|
|
33
46
|
self._initialized = True
|
|
34
47
|
|
|
35
48
|
def get_driver(self) -> Driver:
|
|
36
|
-
"""
|
|
49
|
+
"""
|
|
50
|
+
Gets the Neo4j driver instance, creating it if it doesn't exist.
|
|
51
|
+
This method is thread-safe.
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
ValueError: If Neo4j credentials are not set in environment variables.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
The active Neo4j Driver instance.
|
|
58
|
+
"""
|
|
37
59
|
if self._driver is None:
|
|
38
60
|
with self._lock:
|
|
39
61
|
if self._driver is None:
|
|
62
|
+
# Ensure all necessary credentials are provided.
|
|
40
63
|
if not all([self.neo4j_uri, self.neo4j_username, self.neo4j_password]):
|
|
41
64
|
raise ValueError(
|
|
42
65
|
"Neo4j credentials must be set via environment variables:\n"
|
|
@@ -50,7 +73,7 @@ class DatabaseManager:
|
|
|
50
73
|
self.neo4j_uri,
|
|
51
74
|
auth=(self.neo4j_username, self.neo4j_password)
|
|
52
75
|
)
|
|
53
|
-
# Test the connection
|
|
76
|
+
# Test the connection immediately to fail fast if credentials are wrong.
|
|
54
77
|
try:
|
|
55
78
|
with self._driver.session() as session:
|
|
56
79
|
session.run("RETURN 1").consume()
|
|
@@ -64,7 +87,7 @@ class DatabaseManager:
|
|
|
64
87
|
return self._driver
|
|
65
88
|
|
|
66
89
|
def close_driver(self):
|
|
67
|
-
"""
|
|
90
|
+
"""Closes the Neo4j driver connection if it exists."""
|
|
68
91
|
if self._driver is not None:
|
|
69
92
|
with self._lock:
|
|
70
93
|
if self._driver is not None:
|
|
@@ -73,7 +96,7 @@ class DatabaseManager:
|
|
|
73
96
|
self._driver = None
|
|
74
97
|
|
|
75
98
|
def is_connected(self) -> bool:
|
|
76
|
-
"""
|
|
99
|
+
"""Checks if the database connection is currently active."""
|
|
77
100
|
if self._driver is None:
|
|
78
101
|
return False
|
|
79
102
|
try:
|
|
@@ -81,4 +104,4 @@ class DatabaseManager:
|
|
|
81
104
|
session.run("RETURN 1").consume()
|
|
82
105
|
return True
|
|
83
106
|
except Exception:
|
|
84
|
-
return False
|
|
107
|
+
return False
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
# src/codegraphcontext/core/jobs.py
|
|
2
|
+
"""
|
|
3
|
+
This module defines the data structures and manager for handling long-running,
|
|
4
|
+
background jobs, such as code indexing.
|
|
5
|
+
"""
|
|
2
6
|
import uuid
|
|
3
7
|
import threading
|
|
4
8
|
from datetime import datetime, timedelta
|
|
@@ -9,7 +13,7 @@ from pathlib import Path
|
|
|
9
13
|
|
|
10
14
|
|
|
11
15
|
class JobStatus(Enum):
|
|
12
|
-
"""
|
|
16
|
+
"""Enumeration for the possible statuses of a background job."""
|
|
13
17
|
PENDING = "pending"
|
|
14
18
|
RUNNING = "running"
|
|
15
19
|
COMPLETED = "completed"
|
|
@@ -18,7 +22,10 @@ class JobStatus(Enum):
|
|
|
18
22
|
|
|
19
23
|
@dataclass
|
|
20
24
|
class JobInfo:
|
|
21
|
-
"""
|
|
25
|
+
"""
|
|
26
|
+
A data class to hold all information about a single background job.
|
|
27
|
+
This makes it easy to track the job's progress, status, and results.
|
|
28
|
+
"""
|
|
22
29
|
job_id: str
|
|
23
30
|
status: JobStatus
|
|
24
31
|
start_time: datetime
|
|
@@ -34,19 +41,20 @@ class JobInfo:
|
|
|
34
41
|
is_dependency: bool = False
|
|
35
42
|
|
|
36
43
|
def __post_init__(self):
|
|
44
|
+
"""Ensures the errors list is initialized after the object is created."""
|
|
37
45
|
if self.errors is None:
|
|
38
46
|
self.errors = []
|
|
39
47
|
|
|
40
48
|
@property
|
|
41
49
|
def progress_percentage(self) -> float:
|
|
42
|
-
"""
|
|
50
|
+
"""Calculates the completion percentage of the job."""
|
|
43
51
|
if self.total_files == 0:
|
|
44
52
|
return 0.0
|
|
45
53
|
return (self.processed_files / self.total_files) * 100
|
|
46
54
|
|
|
47
55
|
@property
|
|
48
56
|
def estimated_time_remaining(self) -> Optional[float]:
|
|
49
|
-
"""
|
|
57
|
+
"""Calculates the estimated time remaining based on the average time per file."""
|
|
50
58
|
if self.status != JobStatus.RUNNING or self.processed_files == 0:
|
|
51
59
|
return None
|
|
52
60
|
elapsed = (datetime.now() - self.start_time).total_seconds()
|
|
@@ -55,13 +63,16 @@ class JobInfo:
|
|
|
55
63
|
return remaining_files * avg_time_per_file
|
|
56
64
|
|
|
57
65
|
class JobManager:
|
|
58
|
-
"""
|
|
66
|
+
"""
|
|
67
|
+
A thread-safe manager for creating, updating, and retrieving information
|
|
68
|
+
about background jobs. It stores job information in memory.
|
|
69
|
+
"""
|
|
59
70
|
def __init__(self):
|
|
60
71
|
self.jobs: Dict[str, JobInfo] = {}
|
|
61
|
-
self.lock = threading.Lock()
|
|
72
|
+
self.lock = threading.Lock() # A lock to ensure thread-safe access to the jobs dictionary.
|
|
62
73
|
|
|
63
74
|
def create_job(self, path: str, is_dependency: bool = False) -> str:
|
|
64
|
-
"""
|
|
75
|
+
"""Creates a new job, assigns it a unique ID, and stores it."""
|
|
65
76
|
job_id = str(uuid.uuid4())
|
|
66
77
|
with self.lock:
|
|
67
78
|
self.jobs[job_id] = JobInfo(
|
|
@@ -74,7 +85,7 @@ class JobManager:
|
|
|
74
85
|
return job_id
|
|
75
86
|
|
|
76
87
|
def update_job(self, job_id: str, **kwargs):
|
|
77
|
-
"""
|
|
88
|
+
"""Updates the information for a specific job in a thread-safe manner."""
|
|
78
89
|
with self.lock:
|
|
79
90
|
if job_id in self.jobs:
|
|
80
91
|
job = self.jobs[job_id]
|
|
@@ -83,17 +94,17 @@ class JobManager:
|
|
|
83
94
|
setattr(job, key, value)
|
|
84
95
|
|
|
85
96
|
def get_job(self, job_id: str) -> Optional[JobInfo]:
|
|
86
|
-
"""
|
|
97
|
+
"""Retrieves the information for a single job."""
|
|
87
98
|
with self.lock:
|
|
88
99
|
return self.jobs.get(job_id)
|
|
89
100
|
|
|
90
101
|
def list_jobs(self) -> List[JobInfo]:
|
|
91
|
-
"""
|
|
102
|
+
"""Returns a list of all jobs currently in the manager."""
|
|
92
103
|
with self.lock:
|
|
93
104
|
return list(self.jobs.values())
|
|
94
105
|
|
|
95
106
|
def find_active_job_by_path(self, path: str) -> Optional[JobInfo]:
|
|
96
|
-
"""Finds the most recent, active job for a given path."""
|
|
107
|
+
"""Finds the most recent, currently active (pending or running) job for a given path."""
|
|
97
108
|
with self.lock:
|
|
98
109
|
path_obj = Path(path).resolve()
|
|
99
110
|
|
|
@@ -110,7 +121,7 @@ class JobManager:
|
|
|
110
121
|
return None
|
|
111
122
|
|
|
112
123
|
def cleanup_old_jobs(self, max_age_hours: int = 24):
|
|
113
|
-
"""
|
|
124
|
+
"""Removes old, completed jobs from memory to prevent memory leaks."""
|
|
114
125
|
cutoff_time = datetime.now() - timedelta(hours=max_age_hours)
|
|
115
126
|
with self.lock:
|
|
116
127
|
jobs_to_remove = [
|
|
@@ -118,4 +129,4 @@ class JobManager:
|
|
|
118
129
|
if job.end_time and job.end_time < cutoff_time
|
|
119
130
|
]
|
|
120
131
|
for job_id in jobs_to_remove:
|
|
121
|
-
del self.jobs[job_id]
|
|
132
|
+
del self.jobs[job_id]
|