Skip to content

Commit dd60469

Browse files
authored
Async timeout issue (#53)
There's an issue with the way we have implemented the async check, it needs a timeout to exit (early) --------- Signed-off-by: Ivan Pedrazas <ipedrazas@gmail.com>
1 parent bf15334 commit dd60469

5 files changed

Lines changed: 62 additions & 76 deletions

File tree

runtime_hook.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# ruff: noqa
12
# Runtime hook to ensure stdlib modules are available
23
import pickletools
34
import pickle

scripts/bump_version.py

Lines changed: 28 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66

77
import argparse
88
import os
9+
from pathlib import Path
910
import re
1011
import shutil
1112
import subprocess
1213
import sys
13-
from pathlib import Path
1414

1515

1616
def find_uv():
@@ -42,19 +42,19 @@ def get_latest_tag():
4242
if result.returncode != 0 or not result.stdout.strip():
4343
return None
4444

45-
tags = result.stdout.strip().split('\n')
45+
tags = result.stdout.strip().split("\n")
4646
for tag in tags:
4747
# Find first tag that matches semver pattern
48-
if re.match(r'^v?\d+\.\d+\.\d+$', tag):
48+
if re.match(r"^v?\d+\.\d+\.\d+$", tag):
4949
return tag
5050
return None
5151

5252

5353
def parse_version(version_str):
5454
"""Parse version string into major, minor, patch."""
5555
# Remove 'v' prefix if present
56-
version_str = version_str.lstrip('v')
57-
match = re.match(r'^(\d+)\.(\d+)\.(\d+)$', version_str)
56+
version_str = version_str.lstrip("v")
57+
match = re.match(r"^(\d+)\.(\d+)\.(\d+)$", version_str)
5858
if not match:
5959
raise ValueError(f"Invalid version format: {version_str}")
6060
return tuple(map(int, match.groups()))
@@ -64,11 +64,11 @@ def bump_version(version, bump_type):
6464
"""Bump version based on type (major, minor, patch)."""
6565
major, minor, patch = version
6666

67-
if bump_type == 'major':
67+
if bump_type == "major":
6868
return (major + 1, 0, 0)
69-
elif bump_type == 'minor':
69+
elif bump_type == "minor":
7070
return (major, minor + 1, 0)
71-
elif bump_type == 'patch':
71+
elif bump_type == "patch":
7272
return (major, minor, patch + 1)
7373
else:
7474
raise ValueError(f"Invalid bump type: {bump_type}")
@@ -92,11 +92,7 @@ def update_pyproject_toml(new_version):
9292

9393
# Update version line
9494
updated_content = re.sub(
95-
r'^version\s*=\s*"[^"]+"',
96-
f'version = "{new_version}"',
97-
content,
98-
count=1,
99-
flags=re.MULTILINE
95+
r'^version\s*=\s*"[^"]+"', f'version = "{new_version}"', content, count=1, flags=re.MULTILINE
10096
)
10197

10298
if content == updated_content:
@@ -110,7 +106,7 @@ def update_pyproject_toml(new_version):
110106
def create_and_push_tag(tag_name, push=True):
111107
"""Create git tag and optionally push to remote."""
112108
# Create the tag
113-
result = run_command(f'git tag {tag_name}', check=False)
109+
result = run_command(f"git tag {tag_name}", check=False)
114110
if result.returncode != 0:
115111
print(f"Error creating tag: {result.stderr}", file=sys.stderr)
116112
return False
@@ -119,57 +115,35 @@ def create_and_push_tag(tag_name, push=True):
119115

120116
if push:
121117
# Push the tag to remote
122-
result = run_command(f'git push origin {tag_name}', check=False)
118+
result = run_command(f"git push origin {tag_name}", check=False)
123119
if result.returncode != 0:
124120
print(f"Error pushing tag: {result.stderr}", file=sys.stderr)
125121
# Delete the local tag if push failed
126-
run_command(f'git tag -d {tag_name}', check=False)
122+
run_command(f"git tag -d {tag_name}", check=False)
127123
return False
128124
print(f"Pushed tag to remote: {tag_name}")
129125

130126
return True
131127

132128

133129
def main():
134-
parser = argparse.ArgumentParser(
135-
description="Bump version in git tags and pyproject.toml"
136-
)
137-
parser.add_argument(
138-
'--major',
139-
action='store_true',
140-
help='Bump major version (x.0.0)'
141-
)
142-
parser.add_argument(
143-
'--minor',
144-
action='store_true',
145-
help='Bump minor version (0.x.0)'
146-
)
147-
parser.add_argument(
148-
'--patch',
149-
action='store_true',
150-
help='Bump patch version (0.0.x) - default'
151-
)
152-
parser.add_argument(
153-
'--no-push',
154-
action='store_true',
155-
help='Do not push tag to remote'
156-
)
157-
parser.add_argument(
158-
'--dry-run',
159-
action='store_true',
160-
help='Show what would be done without making changes'
161-
)
130+
parser = argparse.ArgumentParser(description="Bump version in git tags and pyproject.toml")
131+
parser.add_argument("--major", action="store_true", help="Bump major version (x.0.0)")
132+
parser.add_argument("--minor", action="store_true", help="Bump minor version (0.x.0)")
133+
parser.add_argument("--patch", action="store_true", help="Bump patch version (0.0.x) - default")
134+
parser.add_argument("--no-push", action="store_true", help="Do not push tag to remote")
135+
parser.add_argument("--dry-run", action="store_true", help="Show what would be done without making changes")
162136

163137
args = parser.parse_args()
164138

165139
# Determine bump type
166-
bump_type = 'patch' # default
140+
bump_type = "patch" # default
167141
if args.major:
168-
bump_type = 'major'
142+
bump_type = "major"
169143
elif args.minor:
170-
bump_type = 'minor'
144+
bump_type = "minor"
171145
elif args.patch:
172-
bump_type = 'patch'
146+
bump_type = "patch"
173147

174148
# Get latest tag
175149
latest_tag = get_latest_tag()
@@ -192,7 +166,7 @@ def main():
192166
print(f"\n[DRY RUN] Would update pyproject.toml to version: {new_version_str}")
193167
print(f"[DRY RUN] Would create tag: {new_tag}")
194168
if not args.no_push:
195-
print(f"[DRY RUN] Would push tag to remote")
169+
print("[DRY RUN] Would push tag to remote")
196170
return
197171

198172
# Update pyproject.toml
@@ -203,13 +177,13 @@ def main():
203177
# Update uv lock file
204178
print("Updating uv.lock...")
205179
uv_cmd = find_uv()
206-
result = run_command(f'{uv_cmd} lock', check=False)
180+
result = run_command(f"{uv_cmd} lock", check=False)
207181
if result.returncode != 0:
208182
print(f"Error updating uv.lock: {result.stderr}", file=sys.stderr)
209183
sys.exit(1)
210184

211185
# Stage the pyproject.toml and uv.lock changes
212-
result = run_command('git add pyproject.toml uv.lock', check=False)
186+
result = run_command("git add pyproject.toml uv.lock", check=False)
213187
if result.returncode != 0:
214188
print(f"Error staging files: {result.stderr}", file=sys.stderr)
215189
sys.exit(1)
@@ -221,15 +195,15 @@ def main():
221195
print(f"Error committing version change: {result.stderr}", file=sys.stderr)
222196
sys.exit(1)
223197

224-
print(f"Committed version change")
198+
print("Committed version change")
225199

226200
# Create and push tag
227201
if not create_and_push_tag(new_tag, push=not args.no_push):
228202
sys.exit(1)
229203

230204
# Push the commit
231205
if not args.no_push:
232-
result = run_command('git push', check=False)
206+
result = run_command("git push", check=False)
233207
if result.returncode != 0:
234208
print(f"Error pushing commit: {result.stderr}", file=sys.stderr)
235209
sys.exit(1)
@@ -238,5 +212,5 @@ def main():
238212
print(f"\n✓ Successfully bumped version to {new_version_str} ({new_tag})")
239213

240214

241-
if __name__ == '__main__':
215+
if __name__ == "__main__":
242216
main()

src/neptune_mcp/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from dataclasses import dataclass
22
from typing import Any
33

4+
import httpx
45
from neptune_common import (
56
GetLogsResponse,
67
GetProjectResponse,
@@ -11,7 +12,6 @@
1112
QueryDatabaseRequest,
1213
)
1314
import requests
14-
import httpx
1515

1616
from neptune_mcp.config import SETTINGS
1717

src/neptune_mcp/mcp.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,19 @@ def add_new_resource(kind: str) -> dict[str, Any]:
186186
response = client.get_secret_value(SecretId="<aws_id>")
187187
secret = response['SecretString']
188188
```
189+
""",
190+
}
191+
elif kind == "Database":
192+
return {
193+
"description": "Managed database for your applications.",
194+
"neptune_json_configuration": """
195+
To add a database to a project, add the following to 'resources' in 'neptune.json':
196+
```json
197+
{
198+
"kind": "Database",
199+
"name": "<database_name>"
200+
}
201+
```
189202
""",
190203
}
191204
else:
@@ -367,9 +380,9 @@ async def deploy_project(neptune_json_path: str) -> dict[str, Any]:
367380
]
368381
login_process = await asyncio.create_subprocess_shell(
369382
" ".join(login_cmd),
370-
stdin = asyncio.subprocess.PIPE,
371-
stdout = asyncio.subprocess.DEVNULL,
372-
stderr = asyncio.subprocess.STDOUT,
383+
stdin=asyncio.subprocess.PIPE,
384+
stdout=asyncio.subprocess.DEVNULL,
385+
stderr=asyncio.subprocess.STDOUT,
373386
cwd=project_dir,
374387
)
375388

@@ -420,18 +433,20 @@ async def deploy_project(neptune_json_path: str) -> dict[str, Any]:
420433
}
421434

422435
# while deployment.status is not "Deployed", poll every 2 seconds
423-
start_time = time.time()
424436
timeout = 180 # 3 minutes
425-
while deployment.status != "Deployed" and deployment.status != "Error":
426-
if time.time() - start_time > timeout:
427-
log.error(f"Deployment timed out after {timeout} seconds")
428-
return {
429-
"status": "error",
430-
"message": f"deployment timed out after {timeout} seconds while waiting for status 'Deployed'",
431-
"next_step": "check the deployment status with 'get_deployment_status' and investigate any issues",
432-
}
433-
await asyncio.sleep(2)
434-
deployment = await client.get_deployment_async(project_request.name, revision=deployment.revision)
437+
try:
438+
async with asyncio.timeout(timeout):
439+
while deployment.status != "Deployed" and deployment.status != "Error":
440+
await asyncio.sleep(2)
441+
async with asyncio.timeout(10):
442+
deployment = await client.get_deployment_async(project_request.name, revision=deployment.revision)
443+
except TimeoutError:
444+
log.error(f"Deployment timed out after {timeout} seconds")
445+
return {
446+
"status": "error",
447+
"message": f"deployment timed out after {timeout} seconds while waiting for status 'Deployed'",
448+
"next_step": "check the deployment status with 'get_deployment_status' and investigate any issues",
449+
}
435450

436451
if deployment.status != "Deployed":
437452
log.error(f"Deployment failed with status: {deployment.status}")

src/neptune_mcp/utils.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import asyncio
22
from dataclasses import dataclass
3-
import subprocess
4-
from typing import List, Union
53

64

75
@dataclass
@@ -15,9 +13,7 @@ def success(self) -> bool:
1513
return self.returncode == 0
1614

1715

18-
async def run_command(
19-
command: str | bytes, cwd: str | None = None
20-
) -> CommandResult:
16+
async def run_command(command: str | bytes, cwd: str | None = None) -> CommandResult:
2117
"""
2218
Run a command and capture stdout, stderr, and return code.
2319
@@ -31,8 +27,8 @@ async def run_command(
3127
"""
3228
proc = await asyncio.create_subprocess_shell(
3329
command,
34-
stderr = asyncio.subprocess.PIPE,
35-
stdout = asyncio.subprocess.PIPE,
30+
stderr=asyncio.subprocess.PIPE,
31+
stdout=asyncio.subprocess.PIPE,
3632
cwd=cwd,
3733
)
3834

0 commit comments

Comments
 (0)