Skip to content

Commit ce446bb

Browse files
authored
Migrate type checking from mypy to ty (#237)
* Migrate type checking from mypy to ty * Address ty migration review feedback
1 parent 8359479 commit ce446bb

9 files changed

Lines changed: 61 additions & 284 deletions

File tree

‎.github/workflows/main.yml‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ jobs:
2525
run: uv run --locked pytest
2626
- name: Run pre-commit checks
2727
run: uv run --locked pre-commit run --verbose --all-files --show-diff-on-failure
28-
# FIXME: mypy is failing due to missing types-* packages
29-
# - name: Run mypy
30-
# run: uv run --locked mypy algorithms_keeper/ tests/
28+
- name: Run ty
29+
run: uv run --locked ty check
3130
- name: Upload coverage to Codecov
3231
uses: codecov/codecov-action@v3

‎.gitignore‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
# Tests
88
.pytest_cache/
9-
.mypy_cache/
109
__pycache__/
1110

1211
# Local stuff

‎README.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[![CI](https://github.com/TheAlgorithms/algorithms-keeper/actions/workflows/main.yml/badge.svg)](https://github.com/TheAlgorithms/algorithms-keeper/actions/workflows/main.yml)
55
[![codecov](https://codecov.io/gh/TheAlgorithms/algorithms-keeper/branch/master/graph/badge.svg?token=QYAZ665UJL)](https://codecov.io/gh/TheAlgorithms/algorithms-keeper)
66
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://docs.astral.sh/ruff/)
7-
[![Checked with mypy](https://img.shields.io/static/v1?label=mypy&message=checked&color=2a6db2&labelColor=505050)](http://mypy-lang.org/)
7+
[![ty](https://img.shields.io/badge/ty-2a6db2)](https://docs.astral.sh/ty/)
88

99
</div>
1010

@@ -52,12 +52,13 @@ Some actions of the bot can be triggered using commands:
5252
## Development
5353

5454
Install [uv](https://docs.astral.sh/uv/getting-started/installation/), then set up
55-
the project and run tests from the repository root:
55+
the project, run tests, and check types from the repository root:
5656

5757
```shell
5858
uv sync
5959
uv run pytest
6060
uv run pre-commit run --all-files
61+
uv run ty check
6162
```
6263

6364
Ruff handles Python linting and formatting through the pre-commit hooks.

‎algorithms_keeper/__main__.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,11 @@ async def main(request: web.Request) -> web.Response:
8686
# Give GitHub some time to reach internal consistency.
8787
await asyncio.sleep(1)
8888
if logger.isEnabledFor(logging.DEBUG):
89-
callbacks = [func.__name__ for func in main_router.fetch(event)]
89+
# Router callbacks can be callable instances without __name__.
90+
callbacks = [
91+
getattr(func, "__name__", type(func).__name__)
92+
for func in main_router.fetch(event)
93+
]
9094
logger.debug("event=%s callbacks=%s", event_info, callbacks)
9195
await main_router.dispatch(event, gh)
9296
if gh.rate_limit is not None: # pragma: no cover

‎algorithms_keeper/parser/rules/naming_convention.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def valid(self, name: str) -> bool:
4444

4545

4646
class NamingConventionRule(lint_rule.ReviewLintRule):
47-
METADATA_DEPENDENCIES = (QualifiedNameProvider,) # type: ignore
47+
METADATA_DEPENDENCIES = (QualifiedNameProvider,)
4848

4949
VALID = [
5050
Valid("type_hint: str"),
@@ -152,7 +152,7 @@ def visit_AnnAssign(self, node: cst.AnnAssign) -> None:
152152
def visit_AssignTarget(self, node: cst.AssignTarget) -> None:
153153
self._assigntarget_counter += 1
154154

155-
def leave_AssignTarget(self, node: cst.AssignTarget) -> None:
155+
def leave_AssignTarget(self, original_node: cst.AssignTarget) -> None:
156156
self._assigntarget_counter -= 1
157157

158158
def visit_ClassDef(self, node: cst.ClassDef) -> None:

‎pyproject.toml‎

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,24 @@ dependencies = [
1515

1616
[dependency-groups]
1717
dev = [
18-
"mypy",
1918
"pre-commit",
2019
"pytest==9.1.1",
2120
"pytest-aiohttp==1.1.1",
2221
"pytest-asyncio==1.4.0",
2322
"pytest-cov==7.1.0",
23+
"ty==0.0.78",
2424
]
2525

2626
[tool.uv]
2727
package = false
2828

29-
[tool.mypy]
30-
ignore_missing_imports = true
31-
warn_unused_configs = true
32-
warn_unused_ignores = true
33-
warn_redundant_casts = true
34-
warn_return_any = true
35-
check_untyped_defs = true
36-
disallow_untyped_defs = true
37-
disallow_untyped_calls = true
38-
disallow_untyped_decorators = true
39-
disallow_subclassing_any = true
40-
no_implicit_optional = true
29+
[tool.ty.src]
30+
include = ["algorithms_keeper", "tests"]
31+
# Parser fixtures intentionally omit required annotations.
32+
exclude = ["tests/data"]
4133

42-
[[tool.mypy.overrides]]
43-
module = "tests.data.*"
44-
disallow_untyped_defs = false
45-
check_untyped_defs = false
34+
[tool.ty.terminal]
35+
error-on-warning = true
4636

4737
[tool.ruff]
4838
line-length = 88

‎tests/test_main.py‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
15
import pytest
26
import pytest_asyncio
37
from aiohttp import web
@@ -6,9 +10,13 @@
610

711
from .utils import number
812

13+
if TYPE_CHECKING:
14+
from aiohttp.test_utils import TestClient
15+
from pytest_aiohttp.plugin import AiohttpClient
16+
917

1018
@pytest_asyncio.fixture()
11-
async def client(aiohttp_client): # type: ignore
19+
async def client(aiohttp_client: AiohttpClient) -> TestClient:
1220
app = web.Application()
1321
app.router.add_get("/", main.index)
1422
app.router.add_get("/health", main.health)
@@ -17,7 +25,7 @@ async def client(aiohttp_client): # type: ignore
1725

1826

1927
@pytest.mark.asyncio
20-
async def test_ping(client): # type: ignore
28+
async def test_ping(client: TestClient) -> None:
2129
headers = {"X-GitHub-Event": "ping", "X-GitHub-Delivery": "1234"}
2230
data = {"zen": "testing is good"}
2331
response = await client.post("/", headers=headers, json=data)
@@ -26,15 +34,15 @@ async def test_ping(client): # type: ignore
2634

2735

2836
@pytest.mark.asyncio
29-
async def test_failure(client): # type: ignore
37+
async def test_failure(client: TestClient) -> None:
3038
# Even in the face of an exception, the server should not crash.
3139
# Missing key headers.
3240
response = await client.post("/", headers={})
3341
assert response.status == 500
3442

3543

3644
@pytest.mark.asyncio
37-
async def test_success(client): # type: ignore
45+
async def test_success(client: TestClient) -> None:
3846
headers = {"X-GitHub-Event": "project", "X-GitHub-Delivery": "1234"}
3947
# Sending a payload that shouldn't trigger any networking, but no errors
4048
# either.
@@ -44,15 +52,15 @@ async def test_success(client): # type: ignore
4452

4553

4654
@pytest.mark.asyncio
47-
async def test_index(client): # type: ignore
55+
async def test_index(client: TestClient) -> None:
4856
response = await client.get("/")
4957
assert response.status == 200
5058
assert response.headers["content-type"] == "text/html"
5159
assert "algorithms-keeper" in (await response.text())
5260

5361

5462
@pytest.mark.asyncio
55-
async def test_health(client): # type: ignore
63+
async def test_health(client: TestClient) -> None:
5664
response = await client.get("/health")
5765
assert response.status == 200
5866
assert await response.text() == "OK"

‎tests/test_rules.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def test_rules(
100100
"\n".join(str(e) for e in reports),
101101
)
102102

103-
report = reports[0] # type: ignore
103+
report = reports[0]
104104

105105
if test_case.range is not None:
106106
assert test_case.range == report.range

0 commit comments

Comments
 (0)