Skip to content

Commit fd8c2a2

Browse files
committed
Change linters to Ruff and add CI job
1 parent 8c66b94 commit fd8c2a2

22 files changed

+379
-621
lines changed

.github/scripts/increment_version.py

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -97,17 +97,13 @@ def extract_version(pyproject_content: str):
9797
return VersionLine(old_line=version_line, version_str=version_part)
9898

9999

100-
def increment_version_at_pyproject(
101-
pyproject_path: str, inc_type: str, with_beta: bool
102-
) -> str:
100+
def increment_version_at_pyproject(pyproject_path: str, inc_type: str, with_beta: bool) -> str:
103101
with open(pyproject_path, "rt") as f:
104102
setup_content = f.read()
105103

106104
version = extract_version(setup_content)
107105
version.increment(inc_type, with_beta)
108-
setup_content = setup_content.replace(
109-
version.old_line, version.version_line_with_mark()
110-
)
106+
setup_content = setup_content.replace(version.old_line, version.version_line_with_mark())
111107

112108
with open(pyproject_path, "w") as f:
113109
f.write(setup_content)
@@ -143,9 +139,7 @@ def main():
143139
help="increment version type: patch or minor",
144140
choices=["minor", "patch"],
145141
)
146-
parser.add_argument(
147-
"--beta", choices=["true", "false"], help="is beta version"
148-
)
142+
parser.add_argument("--beta", choices=["true", "false"], help="is beta version")
149143
parser.add_argument(
150144
"--changelog-path",
151145
default=DEFAULT_CHANGELOG_PATH,
@@ -158,13 +152,11 @@ def main():
158152

159153
is_beta = args.beta == "true"
160154

161-
new_version = increment_version_at_pyproject(
162-
args.pyproject_path, args.inc_type, is_beta
163-
)
155+
new_version = increment_version_at_pyproject(args.pyproject_path, args.inc_type, is_beta)
164156
add_changelog_version(args.changelog_path, new_version)
165157
set_version_in_version_file(DEFAULT_YDB_VERSION_FILE, new_version)
166158
print(new_version)
167159

168160

169161
if __name__ == "__main__":
170-
main()
162+
main()

.github/workflows/run-linters.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Linters
2+
3+
on:
4+
push:
5+
branches:
6+
- '**'
7+
pull_request_target:
8+
branches:
9+
- '**'
10+
workflow_dispatch:
11+
12+
jobs:
13+
test:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Checkout code
17+
uses: actions/checkout@v4
18+
- name: Set up Python
19+
uses: actions/setup-python@v4
20+
with:
21+
python-version: '3.13'
22+
- name: Install dependencies
23+
run: |
24+
python -m pip install --upgrade pip
25+
- name: Run tests
26+
run: make lint

Makefile

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,12 @@ run-server:
4343

4444
# Run lint checks
4545
lint: dev
46-
flake8 ydb_mcp tests
46+
ruff check ydb_mcp tests
4747
mypy ydb_mcp
48-
black --check ydb_mcp tests
49-
isort --check-only --profile black ydb_mcp tests
5048

5149
# Format code
5250
format: dev
53-
black ydb_mcp tests
54-
isort --profile black ydb_mcp tests
51+
ruff format ydb_mcp tests
5552

5653
# Install package
5754
install:

pyproject.toml

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,35 @@ dependencies = [
2727

2828
[project.optional-dependencies]
2929
dev = [
30-
"pytest>=7.0.0",
31-
"black>=22.0.0",
32-
"isort>=5.0.0",
33-
"mypy>=0.9.0",
30+
"pytest>=7.3.1",
31+
"pytest-asyncio>=0.21.0",
32+
"pytest-cov>=4.1.0",
33+
"pytest-assume>=2.4.3",
34+
"mypy>=1.3.0",
35+
"ruff>=0.11.0",
36+
"docker>=7.0.0",
3437
]
3538

3639
[project.scripts]
3740
ydb-mcp = "ydb_mcp.__main__:main"
3841

39-
[tool.black]
40-
line-length = 100
41-
target-version = ["py38"]
42+
[tool.ruff]
43+
line-length = 121
44+
target-version = "py310"
4245

43-
[tool.isort]
44-
profile = "black"
45-
line_length = 100
46+
[tool.ruff.lint]
47+
select = [
48+
"E", # pycodestyle
49+
"F", # pyflakes
50+
"I", # isort
51+
# TODO: extend with more rules
52+
]
4653

4754
[tool.mypy]
48-
python_version = "3.8"
55+
python_version = "3.10"
4956
warn_return_any = true
50-
warn_unused_configs = true
57+
warn_unused_configs = true
58+
59+
[[tool.mypy.overrides]]
60+
module = "ydb.*"
61+
ignore_missing_imports = true

requirements-dev.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ pytest>=7.3.1
33
pytest-asyncio>=0.21.0
44
pytest-cov>=4.1.0
55
pytest-assume>=2.4.3
6-
black>=23.3.0
7-
isort>=5.12.0
86
mypy>=1.3.0
9-
flake8>=6.0.0
10-
httpx>=0.24.0
7+
ruff>=0.11.0
118
docker>=7.0.0 # For YDB Docker container management in tests

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Pytest configuration for testing YDB MCP server."""
22

33
import os
4-
from unittest.mock import AsyncMock, MagicMock, patch
4+
from unittest.mock import AsyncMock, patch
55

66
import pytest
77

tests/docker_utils.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import logging
22
import os
3-
import platform
43
import socket
54
import time
65

tests/integration/conftest.py

Lines changed: 22 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from urllib.parse import urlparse
1616

1717
import pytest
18-
import ydb
1918

2019
from tests.docker_utils import start_ydb_container, stop_container, wait_for_port
2120
from ydb_mcp.server import AUTH_MODE_ANONYMOUS, YDBMCPServer
@@ -237,9 +236,7 @@ async def session_mcp_server(ydb_server):
237236

238237
if _mcp_server_instance is None:
239238
# Create the server with anonymous credentials
240-
_mcp_server_instance = YDBMCPServer(
241-
endpoint=YDB_ENDPOINT, database=YDB_DATABASE, auth_mode=AUTH_MODE_ANONYMOUS
242-
)
239+
_mcp_server_instance = YDBMCPServer(endpoint=YDB_ENDPOINT, database=YDB_DATABASE, auth_mode=AUTH_MODE_ANONYMOUS)
243240

244241
try:
245242
# Ensure we have a valid event loop
@@ -278,46 +275,32 @@ async def session_mcp_server(ydb_server):
278275
await cleanup_pending_tasks()
279276

280277

281-
@pytest.fixture(scope="function")
282-
async def mcp_server(session_mcp_server):
283-
"""Provide a clean MCP server connection for each test by restarting the connection."""
284-
if session_mcp_server is None:
285-
pytest.fail("Could not get a valid MCP server instance")
286-
return
278+
@pytest.fixture(scope="session")
279+
async def mcp_server(ydb_server): # noqa: F811
280+
"""Create a YDB MCP server instance for testing."""
281+
# Create the server with anonymous credentials
282+
server = YDBMCPServer(endpoint=YDB_ENDPOINT, database=YDB_DATABASE)
287283

288-
# Reset server state to default
289-
session_mcp_server.auth_mode = AUTH_MODE_ANONYMOUS
290-
session_mcp_server.login = None
291-
session_mcp_server.password = None
284+
# Store the event loop
285+
server._loop = ensure_event_loop()
292286

293287
try:
294-
# Clean up any leftover tasks before restart
295-
await cleanup_pending_tasks()
296-
297-
# Restart the connection to ensure clean environment for the test
298-
if session_mcp_server.driver is not None:
299-
logger.info("Restarting YDB connection for clean test environment")
300-
await session_mcp_server.restart()
288+
# Initialize the server by creating the driver
289+
await server.create_driver()
290+
yield server
301291

302-
yield session_mcp_server
292+
# Clean up after tests
293+
logger.info("Cleaning up YDB server resources after tests")
294+
await cleanup_pending_tasks()
295+
if server.driver:
296+
await cleanup_driver(server.driver)
303297

304298
except Exception as e:
305-
logger.error(f"Error during test setup: {e}")
306-
pytest.fail(f"Failed to setup test environment: {e}")
299+
logger.error(f"Failed to initialize YDB MCP server: {e}")
300+
pytest.fail(f"Failed to initialize YDB MCP server: {e}")
307301
finally:
308-
# Reset server state after test
309-
try:
310-
session_mcp_server.auth_mode = AUTH_MODE_ANONYMOUS
311-
session_mcp_server.login = None
312-
session_mcp_server.password = None
313-
314-
# Clean up any tasks from the test
315-
await cleanup_pending_tasks()
316-
317-
# Restart to clean state
318-
await session_mcp_server.restart()
319-
except Exception as e:
320-
logger.error(f"Error during test cleanup: {e}")
302+
# Final cleanup
303+
await cleanup_pending_tasks()
321304

322305

323306
async def call_mcp_tool(mcp_server, tool_name, **params):
@@ -372,11 +355,7 @@ async def cleanup_after_all_tests():
372355
loop = asyncio.get_running_loop()
373356
if not loop.is_closed():
374357
# Cancel all tasks
375-
pending = [
376-
task
377-
for task in asyncio.all_tasks(loop)
378-
if not task.done() and task != asyncio.current_task()
379-
]
358+
pending = [task for task in asyncio.all_tasks(loop) if not task.done() and task != asyncio.current_task()]
380359

381360
if pending:
382361
logger.debug(f"Cleaning up {len(pending)} pending tasks in final cleanup")
@@ -394,9 +373,7 @@ async def cleanup_after_all_tests():
394373
for task in pending:
395374
if not task.done():
396375
with suppress(asyncio.CancelledError, Exception):
397-
task._log_destroy_pending = (
398-
False # Suppress the warning about task destruction
399-
)
376+
task._log_destroy_pending = False # Suppress the warning about task destruction
400377

401378
# Close the loop
402379
loop.stop()

tests/integration/test_authentication_integration.py

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@
1414
from ydb_mcp.server import AUTH_MODE_ANONYMOUS, AUTH_MODE_LOGIN_PASSWORD
1515

1616
# Suppress the utcfromtimestamp deprecation warning from the YDB library
17-
warnings.filterwarnings(
18-
"ignore", message="datetime.datetime.utcfromtimestamp.*", category=DeprecationWarning
19-
)
17+
warnings.filterwarnings("ignore", message="datetime.datetime.utcfromtimestamp.*", category=DeprecationWarning)
2018

2119
# Table name used for tests - using timestamp to avoid conflicts
2220
TEST_TABLE = f"mcp_integration_test_{int(time.time())}"
@@ -58,12 +56,7 @@ async def test_login_password_authentication(mcp_server):
5856
# Verify we can execute a query
5957
result = await call_mcp_tool(mcp_server, "ydb_query", sql="SELECT 1+1 as result")
6058
# Parse the JSON from the 'text' field if present
61-
if (
62-
isinstance(result, list)
63-
and len(result) > 0
64-
and isinstance(result[0], dict)
65-
and "text" in result[0]
66-
):
59+
if isinstance(result, list) and len(result) > 0 and isinstance(result[0], dict) and "text" in result[0]:
6760
parsed = json.loads(result[0]["text"])
6861
else:
6962
parsed = result
@@ -80,12 +73,7 @@ async def test_login_password_authentication(mcp_server):
8073
# Query should fail with auth error
8174
result = await call_mcp_tool(mcp_server, "ydb_query", sql="SELECT 1+1 as result")
8275
# Parse the JSON from the 'text' field if present
83-
if (
84-
isinstance(result, list)
85-
and len(result) > 0
86-
and isinstance(result[0], dict)
87-
and "text" in result[0]
88-
):
76+
if isinstance(result, list) and len(result) > 0 and isinstance(result[0], dict) and "text" in result[0]:
8977
parsed = json.loads(result[0]["text"])
9078
else:
9179
parsed = result
@@ -111,9 +99,9 @@ async def test_login_password_authentication(mcp_server):
11199
# Allow empty error message as valid
112100
pass
113101
else:
114-
assert any(
115-
keyword in error_msg for keyword in all_keywords
116-
), f"Unexpected error message: {parsed.get('error')}"
102+
assert any(keyword in error_msg for keyword in all_keywords), (
103+
f"Unexpected error message: {parsed.get('error')}"
104+
)
117105

118106
finally:
119107
# Switch back to anonymous auth to clean up (fixture will handle final state reset)

0 commit comments

Comments
 (0)