Skip to content

Commit fc7af80

Browse files
committed
Add linters checks to CI
1 parent 88af0e0 commit fc7af80

23 files changed

+379
-651
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

.github/workflows/run-tests.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Run Tests
1+
name: Tests
22

33
on:
44
push:
@@ -22,6 +22,5 @@ jobs:
2222
- name: Install dependencies
2323
run: |
2424
python -m pip install --upgrade pip
25-
pip install -r requirements-dev.txt
2625
- name: Run tests
2726
run: make test

Makefile

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,17 @@ 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:
5855
pip install -e .
5956

6057
# Install development dependencies
6158
dev:
62-
pip install -e ".[dev]"
63-
pip install -r requirements-dev.txt
59+
pip install -e ".[dev]"

pyproject.toml

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,34 @@ dependencies = [
2828
[project.optional-dependencies]
2929
dev = [
3030
"pytest>=7.0.0",
31-
"black>=22.0.0",
32-
"isort>=5.0.0",
31+
"pytest-asyncio>=0.21.0",
32+
"pytest-cov>=4.1.0",
33+
"pytest-assume>=2.4.3",
34+
"ruff>=0.11.0",
3335
"mypy>=0.9.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 & 73 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
@@ -198,34 +197,6 @@ def ydb_server():
198197
stop_container(container)
199198

200199

201-
@pytest.fixture(scope="session")
202-
async def mcp_server(ydb_server):
203-
"""Create a YDB MCP server instance for testing."""
204-
# Create the server with anonymous credentials
205-
server = YDBMCPServer(endpoint=YDB_ENDPOINT, database=YDB_DATABASE)
206-
207-
# Store the event loop
208-
server._loop = ensure_event_loop()
209-
210-
try:
211-
# Initialize the server by creating the driver
212-
await server.create_driver()
213-
yield server
214-
215-
# Clean up after tests
216-
logger.info("Cleaning up YDB server resources after tests")
217-
await cleanup_pending_tasks()
218-
if server.driver:
219-
await cleanup_driver(server.driver)
220-
221-
except Exception as e:
222-
logger.error(f"Failed to initialize YDB MCP server: {e}")
223-
pytest.fail(f"Failed to initialize YDB MCP server: {e}")
224-
finally:
225-
# Final cleanup
226-
await cleanup_pending_tasks()
227-
228-
229200
# Create a global variable to cache the server instance
230201
_mcp_server_instance = None
231202

@@ -237,9 +208,7 @@ async def session_mcp_server(ydb_server):
237208

238209
if _mcp_server_instance is None:
239210
# Create the server with anonymous credentials
240-
_mcp_server_instance = YDBMCPServer(
241-
endpoint=YDB_ENDPOINT, database=YDB_DATABASE, auth_mode=AUTH_MODE_ANONYMOUS
242-
)
211+
_mcp_server_instance = YDBMCPServer(endpoint=YDB_ENDPOINT, database=YDB_DATABASE, auth_mode=AUTH_MODE_ANONYMOUS)
243212

244213
try:
245214
# Ensure we have a valid event loop
@@ -278,46 +247,32 @@ async def session_mcp_server(ydb_server):
278247
await cleanup_pending_tasks()
279248

280249

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
250+
@pytest.fixture(scope="session")
251+
async def mcp_server(ydb_server):
252+
"""Create a YDB MCP server instance for testing."""
253+
# Create the server with anonymous credentials
254+
server = YDBMCPServer(endpoint=YDB_ENDPOINT, database=YDB_DATABASE)
287255

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
256+
# Store the event loop
257+
server._loop = ensure_event_loop()
292258

293259
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()
260+
# Initialize the server by creating the driver
261+
await server.create_driver()
262+
yield server
301263

302-
yield session_mcp_server
264+
# Clean up after tests
265+
logger.info("Cleaning up YDB server resources after tests")
266+
await cleanup_pending_tasks()
267+
if server.driver:
268+
await cleanup_driver(server.driver)
303269

304270
except Exception as e:
305-
logger.error(f"Error during test setup: {e}")
306-
pytest.fail(f"Failed to setup test environment: {e}")
271+
logger.error(f"Failed to initialize YDB MCP server: {e}")
272+
pytest.fail(f"Failed to initialize YDB MCP server: {e}")
307273
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}")
274+
# Final cleanup
275+
await cleanup_pending_tasks()
321276

322277

323278
async def call_mcp_tool(mcp_server, tool_name, **params):
@@ -372,11 +327,7 @@ async def cleanup_after_all_tests():
372327
loop = asyncio.get_running_loop()
373328
if not loop.is_closed():
374329
# 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-
]
330+
pending = [task for task in asyncio.all_tasks(loop) if not task.done() and task != asyncio.current_task()]
380331

381332
if pending:
382333
logger.debug(f"Cleaning up {len(pending)} pending tasks in final cleanup")
@@ -394,9 +345,7 @@ async def cleanup_after_all_tests():
394345
for task in pending:
395346
if not task.done():
396347
with suppress(asyncio.CancelledError, Exception):
397-
task._log_destroy_pending = (
398-
False # Suppress the warning about task destruction
399-
)
348+
task._log_destroy_pending = False # Suppress the warning about task destruction
400349

401350
# Close the loop
402351
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)