Skip to content

Commit 84d1905

Browse files
committed
build: migrate type checker from mypy to pyright (strict)
1 parent 3fcc5fa commit 84d1905

17 files changed

Lines changed: 107 additions & 290 deletions

File tree

.pre-commit-config.yaml

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,25 @@ repos:
5555
# ============================================================================
5656
# STATIC TYPING
5757
# ============================================================================
58-
- repo: https://github.com/pre-commit/mirrors-mypy
59-
rev: v1.18.2
58+
- repo: https://github.com/RobertCraigie/pyright-python
59+
rev: v1.1.408
6060
hooks:
61-
- id: mypy
62-
name: mypy (strict mode)
61+
- id: pyright
62+
name: pyright (strict mode)
63+
pass_filenames: false
6364
additional_dependencies:
64-
- types-PyYAML
65-
- types-aiofiles
66-
- types-colorama
67-
- pytest
68-
- pytest-asyncio
65+
- litellm
66+
- pyyaml
67+
- colorama
68+
- httpx
69+
- tiktoken
70+
- aiofiles
71+
- scikit-learn
72+
- markdown-it-py
73+
- google-auth
6974
- pydantic
70-
files: ^src/
75+
- python-dotenv
76+
- aiohttp
7177

7278
# ============================================================================
7379
# LINT + FORMAT (Ruff: replaces flake8, isort, pyupgrade, and black)

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ Please read our [Code of Conduct](CODE_OF_CONDUCT.md) before participating.
7373
|--------|---------|
7474
| `make dev` | Install dev dependencies + pre-commit hooks |
7575
| `make fmt` | Format + autofix code (`ruff format` + `ruff check --fix`) |
76-
| `make lint` | Lint + format check + type check (ruff, mypy) |
76+
| `make lint` | Lint + format check + type check (ruff, pyright) |
7777
| `make test` | Run tests with coverage |
7878
| `make test-quick` | Run tests without coverage |
7979
| `make clean` | Remove build artifacts |
@@ -106,7 +106,7 @@ We do not use docstrings or inline comments explaining "what" the code does. Ins
106106

107107
### Type Safety
108108

109-
- All code must pass `mypy --strict`
109+
- All code must pass `pyright` in strict mode
110110
- Use proper type annotations on all public functions
111111
- Avoid `Any` unless absolutely necessary
112112

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ lint: ## Run all linters
1919
python scripts/lint_structure.py
2020
ruff check $(PY_SOURCES)
2121
ruff format --check $(PY_SOURCES)
22-
mypy src/
22+
pyright src/
2323

2424
test: ## Run tests with coverage
2525
@echo "Running tests..."

pyproject.toml

Lines changed: 16 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,7 @@ dev = [
5151
"hypothesis~=6.0",
5252
"coverage[toml]~=7.13",
5353
"ruff>=0.15.10,<1.0",
54-
"mypy>=1.11,<3.0",
55-
"types-colorama~=0.4",
56-
"types-PyYAML~=6.0",
57-
"types-aiofiles~=25.1",
54+
"pyright>=1.1.408",
5855
"pre-commit~=4.5",
5956
"mutmut~=3.5",
6057
"radon~=6.0",
@@ -190,39 +187,21 @@ quote-style = "double"
190187
indent-style = "space"
191188
line-ending = "lf"
192189

193-
[tool.mypy]
194-
strict = true
195-
warn_unreachable = true
196-
warn_redundant_casts = true
197-
warn_unused_ignores = false
198-
disallow_any_generics = true
199-
disallow_untyped_defs = true
200-
disallow_incomplete_defs = true
201-
check_untyped_defs = true
202-
no_implicit_optional = true
203-
files = ["src/"]
204-
exclude = ["tests/", "^docs/", "^benchmarks/"]
205-
206-
[[tool.mypy.overrides]]
207-
module = [
208-
"litellm", "litellm.*",
209-
"sklearn", "sklearn.*",
210-
"markdown_it", "markdown_it.*",
211-
"httpx", "httpx.*",
212-
"dotenv", "dotenv.*",
213-
"yaml", "yaml.*",
214-
"colorama", "colorama.*",
215-
"pydantic", "pydantic.*",
216-
"bcrypt", "bcrypt.*",
217-
"jwt", "jwt.*",
218-
"psycopg2", "psycopg2.*",
219-
]
220-
ignore_missing_imports = true
221-
disable_error_code = ["attr-defined"]
222-
223-
[[tool.mypy.overrides]]
224-
module = "certamen.infrastructure.config.models"
225-
disable_error_code = ["misc"]
190+
[tool.pyright]
191+
include = ["src"]
192+
exclude = ["**/__pycache__", "tests", "docs", "benchmarks", "build", "dist"]
193+
pythonVersion = "3.11"
194+
typeCheckingMode = "strict"
195+
# Untyped third-party libs (litellm, sklearn, bcrypt, jwt, psycopg2) ship no
196+
# stubs. The prior mypy config treated them as Any via ignore_missing_imports;
197+
# silencing the Unknown-type reports keeps that stance so strict checking still
198+
# applies fully to our own annotated code without drowning in external noise.
199+
reportMissingTypeStubs = "none"
200+
reportMissingModuleSource = "none"
201+
reportUnknownMemberType = "none"
202+
reportUnknownVariableType = "none"
203+
reportUnknownArgumentType = "none"
204+
reportUnknownLambdaType = "none"
226205

227206
# Mutation testing configuration
228207
# Evidence: Mutation score correlates with real fault detection

src/certamen/application/bootstrap.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import importlib
12
from pathlib import Path
23
from typing import TYPE_CHECKING, Any
34

45
# Import providers to trigger registration at bootstrap time
5-
import certamen.infrastructure.llm.providers # noqa: F401
6+
importlib.import_module("certamen.infrastructure.llm.providers")
67

78
if TYPE_CHECKING:
89
from certamen.engine import Certamen
@@ -235,10 +236,8 @@ async def build_certamen(
235236
f"Invalid configuration provided. Missing or invalid sections:\n - {error_details}\n\n"
236237
f"Required sections: models, retry, features, prompts, outputs_dir"
237238
)
238-
elif isinstance(config, Config):
239-
config_obj = config
240239
else:
241-
raise TypeError(f"Unsupported config type: {type(config)}")
240+
config_obj = config
242241

243242
config_data = config_obj.config_data
244243

src/certamen/application/workflow/nodes/__init__.py

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,27 @@
55
PortType,
66
)
77

8+
_NODE_MODULES = (
9+
"disagreement",
10+
"evaluation",
11+
"flow",
12+
"generation",
13+
"input",
14+
"interrogation",
15+
"knowledge",
16+
"llm",
17+
"output",
18+
"synthesis",
19+
)
20+
821

922
def register_all() -> None:
10-
import certamen.application.workflow.nodes.disagreement
11-
import certamen.application.workflow.nodes.evaluation
12-
import certamen.application.workflow.nodes.flow
13-
import certamen.application.workflow.nodes.generation
14-
import certamen.application.workflow.nodes.input
15-
import certamen.application.workflow.nodes.interrogation
16-
import certamen.application.workflow.nodes.knowledge
17-
import certamen.application.workflow.nodes.llm
18-
import certamen.application.workflow.nodes.output
19-
import certamen.application.workflow.nodes.synthesis
23+
import importlib
2024

21-
del (
22-
certamen.application.workflow.nodes.disagreement,
23-
certamen.application.workflow.nodes.evaluation,
24-
certamen.application.workflow.nodes.flow,
25-
certamen.application.workflow.nodes.generation,
26-
certamen.application.workflow.nodes.input,
27-
certamen.application.workflow.nodes.interrogation,
28-
certamen.application.workflow.nodes.knowledge,
29-
certamen.application.workflow.nodes.llm,
30-
certamen.application.workflow.nodes.output,
31-
certamen.application.workflow.nodes.synthesis,
32-
)
25+
for module_name in _NODE_MODULES:
26+
importlib.import_module(
27+
f"certamen.application.workflow.nodes.{module_name}"
28+
)
3329

3430

3531
__all__ = ["BaseNode", "ExecutionContext", "Port", "PortType", "register_all"]

src/certamen/application/workflow/registry.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ def register(self, node_class: type[BaseNode]) -> type[BaseNode]:
2020
def get(self, node_type: str) -> type[BaseNode] | None:
2121
return self._nodes.get(node_type)
2222

23+
def node_types(self) -> list[str]:
24+
return list(self._nodes.keys())
25+
2326
def create(
2427
self,
2528
node_type: str,

src/certamen/application/workflow/schema.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55

66
def list_node_types() -> list[str]:
7-
return sorted(registry._nodes.keys())
7+
return sorted(registry.node_types())
88

99

1010
def get_node_schema(node_type: str) -> dict[str, Any] | None:
11-
node_class = registry._nodes.get(node_type)
11+
node_class = registry.get(node_type)
1212
if not node_class:
1313
return None
1414

src/certamen/infrastructure/llm/litellm_adapter.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -304,15 +304,17 @@ async def _execute_completion(
304304
prompt_tokens = 0
305305
completion_tokens = 0
306306
total_tokens = 0
307-
if hasattr(response, "usage") and response.usage:
308-
prompt_tokens = getattr(response.usage, "prompt_tokens", 0)
309-
completion_tokens = getattr(response.usage, "completion_tokens", 0)
310-
total_tokens = getattr(response.usage, "total_tokens", 0)
307+
usage = getattr(response, "usage", None)
308+
if usage:
309+
prompt_tokens = getattr(usage, "prompt_tokens", 0)
310+
completion_tokens = getattr(usage, "completion_tokens", 0)
311+
total_tokens = getattr(usage, "total_tokens", 0)
311312

312313
# Extract rate limit headers if available
313314
rate_limit_info = ""
314-
if hasattr(response, "_hidden_params"):
315-
headers = getattr(response._hidden_params, "headers", {})
315+
hidden_params = getattr(response, "_hidden_params", None)
316+
if hidden_params is not None:
317+
headers = getattr(hidden_params, "headers", {})
316318
if headers:
317319
rate_limit_headers = {
318320
k: v

src/certamen/infrastructure/llm/retry.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,9 @@ def _get_backoff_multiplier(error_type: str, provider: str) -> float:
6060
provider_mult = multiplier_value.get(
6161
provider, multiplier_value["default"]
6262
)
63-
return float(provider_mult) if provider_mult is not None else 1.5
63+
return float(provider_mult)
6464

65-
if isinstance(multiplier_value, (int, float)):
66-
return float(multiplier_value)
67-
68-
return 1.5
65+
return float(multiplier_value)
6966

7067

7168
def _get_jitter_range(error_type: str, provider: str) -> float:

0 commit comments

Comments
 (0)