Skip to content

Commit 68fa314

Browse files
OPpuolitaivalclaude
andcommitted
Replace mypy with ty type checker
Switch from mypy to ty (v0.0.17, by Astral) for type checking. ty is faster and from the same team as ruff/uv, simplifying the toolchain. - Replace mypy with ty in dev dependencies and deduplicate pytest entry - Remove [tool.mypy] config, add [tool.ty] config in pyproject.toml - Update scripts (license_to_commit.sh, test_all_versions.sh) and docs - Remove unused mypy-specific type: ignore comments from decorators.py - Add ty-specific suppression comments for dynamic attribute patterns - Add explicit import of importlib.machinery in main.py - Fix pytest_pyosmo plugin using semantic names instead of function names - Fix license_to_commit.sh to use uv run for all commands - Add None checks in test_end_conditions.py for type safety Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8f07d0b commit 68fa314

13 files changed

Lines changed: 71 additions & 122 deletions

CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ ruff format pyosmo/
4949
ruff format --check pyosmo/
5050

5151
# Type checking
52-
mypy pyosmo/
52+
ty check pyosmo/
5353
```
5454

5555
### Installation & Building
@@ -202,7 +202,7 @@ See `doc/strategic_vision_2025.md` for detailed roadmap and competitive analysis
202202

203203
1. Make changes to `pyosmo/` code
204204
2. Run `ruff check pyosmo/ --fix` and `ruff format pyosmo/` to lint and format
205-
3. Run `mypy pyosmo/` for type checking
205+
3. Run `ty check pyosmo/` for type checking
206206
4. Run `pytest pyosmo/tests/` to verify tests pass
207207
5. Add/update tests in `pyosmo/tests/` for new functionality
208208
6. Verify coverage with `pytest pyosmo/tests/ --cov=pyosmo`

doc/development.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ ruff format --check pyosmo/
5454
### Run type checking
5555

5656
```bash
57-
mypy pyosmo/
57+
ty check pyosmo/
5858
```
5959

6060
### Run mutation testing

doc/documentation_improvements.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ ruff format .
286286

287287
### Type Checking
288288
```bash
289-
mypy pyosmo/
289+
ty check pyosmo/
290290
```
291291

292292
## Testing

doc/improvement_roadmap.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ This document provides a comprehensive roadmap for improving pyosmo based on:
8585
- [ ] Add type hints to algorithm classes
8686
- [ ] Add type hints to end_conditions classes
8787
- [ ] Add type hints to error_strategy classes
88-
- [ ] Run mypy for validation
89-
- [ ] Add mypy to CI/CD pipeline
88+
- [ ] Run ty for validation
89+
- [ ] Add ty to CI/CD pipeline
9090

9191
#### 1.6 Docstring Coverage
9292
**Status**: Only 25.5% documented

license_to_commit.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ set -e
44
uv sync --all-extras
55
uv run ruff check --fix
66
uv run ruff format
7-
uv run mypy pyosmo/
8-
uv run mypy examples/
7+
uv run ty check pyosmo/
8+
uv run ty check examples/
99
uv run pytest pyosmo/tests/
10-
pytest pytest_pyosmo/test_models_example.py
10+
uv run pytest pytest_pyosmo/test_models_example.py

pyosmo/decorators.py

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def weight(value: int | float) -> Callable[[F], F]:
1010
"""Make able to put weight in classes or functions by decorator @weight"""
1111

1212
def decorator(func: F) -> F:
13-
func.weight = value # type: ignore[attr-defined]
13+
func.weight = value
1414
return func
1515

1616
return decorator
@@ -34,11 +34,11 @@ def __call__(self, func: Callable[..., Any]) -> Callable[..., Any]:
3434
def wrapper(*args: Any, **kwargs: Any) -> Any:
3535
return func(*args, **kwargs)
3636

37-
# Attach metadata
38-
wrapper._osmo_step = True # type: ignore[attr-defined]
39-
wrapper._osmo_step_name = self.name or func.__name__ # type: ignore[attr-defined]
40-
wrapper._osmo_weight = self.weight_value # type: ignore[attr-defined]
41-
wrapper._osmo_enabled = self.enabled # type: ignore[attr-defined]
37+
# Attach metadata (dynamic attributes for model discovery)
38+
wrapper._osmo_step = True # type: ignore[unresolved-attribute]
39+
wrapper._osmo_step_name = self.name or func.__name__ # type: ignore[unresolved-attribute]
40+
wrapper._osmo_weight = self.weight_value # type: ignore[unresolved-attribute]
41+
wrapper._osmo_enabled = self.enabled # type: ignore[unresolved-attribute]
4242

4343
return wrapper
4444

@@ -102,17 +102,17 @@ def can_process(self) -> bool:
102102
guard_func = step_name_or_func
103103

104104
def inline_decorator(func: Callable[..., Any]) -> Callable[..., Any]:
105-
func._osmo_guard_inline = guard_func # type: ignore[attr-defined]
106-
func._osmo_guard_invert = invert # type: ignore[attr-defined]
105+
func._osmo_guard_inline = guard_func # type: ignore[unresolved-attribute]
106+
func._osmo_guard_invert = invert # type: ignore[unresolved-attribute]
107107
return func
108108

109109
return inline_decorator
110110

111111
# Named guard
112112
def named_decorator(func: Callable[..., Any]) -> Callable[..., Any]:
113-
func._osmo_guard = True # type: ignore[attr-defined]
114-
func._osmo_guard_for = step_name_or_func # type: ignore[attr-defined]
115-
func._osmo_guard_invert = invert # type: ignore[attr-defined]
113+
func._osmo_guard = True # type: ignore[unresolved-attribute]
114+
func._osmo_guard_for = step_name_or_func # type: ignore[unresolved-attribute]
115+
func._osmo_guard_invert = invert # type: ignore[unresolved-attribute]
116116
return func
117117

118118
return named_decorator
@@ -134,8 +134,8 @@ def before_login(self):
134134
"""
135135

136136
def decorator(func: F) -> F:
137-
func._osmo_pre = True # type: ignore[attr-defined]
138-
func._osmo_pre_for = step_name # type: ignore[attr-defined]
137+
func._osmo_pre = True
138+
func._osmo_pre_for = step_name
139139
return func
140140

141141
return decorator
@@ -157,8 +157,8 @@ def after_login(self):
157157
"""
158158

159159
def decorator(func: F) -> F:
160-
func._osmo_post = True # type: ignore[attr-defined]
161-
func._osmo_post_for = step_name # type: ignore[attr-defined]
160+
func._osmo_post = True
161+
func._osmo_post_for = step_name
162162
return func
163163

164164
return decorator
@@ -181,7 +181,7 @@ def checkout(self):
181181
"""
182182

183183
def decorator(func: F) -> F:
184-
func._osmo_requires = list(requirements) # type: ignore[attr-defined]
184+
func._osmo_requires = list(requirements)
185185
return func
186186

187187
return decorator
@@ -204,8 +204,8 @@ def complete_order(self):
204204
"""
205205

206206
def decorator(func: F) -> F:
207-
func._osmo_requires = list(requirements) # type: ignore[attr-defined]
208-
func._osmo_requires_all = True # type: ignore[attr-defined]
207+
func._osmo_requires = list(requirements)
208+
func._osmo_requires_all = True
209209
return func
210210

211211
return decorator
@@ -228,8 +228,8 @@ def check_auth(self):
228228
"""
229229

230230
def decorator(func: F) -> F:
231-
func._osmo_requires = list(requirements) # type: ignore[attr-defined]
232-
func._osmo_requires_any = True # type: ignore[attr-defined]
231+
func._osmo_requires = list(requirements)
232+
func._osmo_requires_any = True
233233
return func
234234

235235
return decorator
@@ -254,9 +254,9 @@ def get_cart_size(self) -> str:
254254
"""
255255

256256
def decorator(func: F) -> F:
257-
func._osmo_variable = True # type: ignore[attr-defined]
258-
func._osmo_variable_name = name # type: ignore[attr-defined]
259-
func._osmo_variable_categories = categories # type: ignore[attr-defined]
257+
func._osmo_variable = True
258+
func._osmo_variable_name = name
259+
func._osmo_variable_categories = categories
260260
return func
261261

262262
return decorator
@@ -276,5 +276,5 @@ def get_state(self) -> str:
276276
Returns:
277277
Decorated function
278278
"""
279-
func._osmo_state = True # type: ignore[attr-defined]
279+
func._osmo_state = True
280280
return func

pyosmo/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import importlib
2+
import importlib.machinery
23
import inspect
34
import sys
45
from logging import getLogger

pyosmo/model.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def default_weight(self) -> float:
2222

2323
@property
2424
def func(self) -> Callable[[], Any]:
25-
return getattr(self.object_instance, self.function_name) # type: ignore[no-any-return]
25+
return getattr(self.object_instance, self.function_name)
2626

2727
def execute(self) -> Any:
2828
try:
@@ -63,7 +63,7 @@ def guard_name(self) -> str:
6363
def weight(self) -> float:
6464
# Check decorator-based weight first
6565
if hasattr(self.func, '_osmo_weight') and self.func._osmo_weight is not None:
66-
return float(self.func._osmo_weight)
66+
return float(self.func._osmo_weight) # type: ignore[arg-type]
6767

6868
# Check weight function (naming convention)
6969
weight_function = self.return_function_if_exists(f'weight_{self.name}')
@@ -72,7 +72,7 @@ def weight(self) -> float:
7272

7373
# Check weight attribute (legacy decorator)
7474
if hasattr(self.func, 'weight'):
75-
return float(self.func.weight)
75+
return float(self.func.weight) # type: ignore[arg-type]
7676

7777
return self.default_weight # Default value
7878

@@ -85,7 +85,7 @@ def is_available(self) -> bool:
8585

8686
# Check for inline guard (decorator-based)
8787
if hasattr(self.func, '_osmo_guard_inline'):
88-
result = bool(self.func._osmo_guard_inline(self.object_instance))
88+
result = bool(self.func._osmo_guard_inline(self.object_instance)) # type: ignore[operator]
8989
# Apply invert if specified
9090
if hasattr(self.func, '_osmo_guard_invert') and self.func._osmo_guard_invert:
9191
result = not result

pyosmo/tests/test_end_conditions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ def test_step_coverage_mid_range():
117117
# With 2 steps and 50% coverage, at least 1 step must have been used
118118
step_first = osmo.model.get_step_by_name('step_first')
119119
step_second = osmo.model.get_step_by_name('step_second')
120+
assert step_first is not None
121+
assert step_second is not None
120122
used_first = osmo.history.get_step_count(step_first) > 0
121123
used_second = osmo.history.get_step_count(step_second) > 0
122124
assert used_first or used_second
@@ -154,5 +156,7 @@ def guard_second(self):
154156
# Suite should stop once both steps have been used across multiple tests
155157
step_first = osmo.model.get_step_by_name('step_first')
156158
step_second = osmo.model.get_step_by_name('step_second')
159+
assert step_first is not None
160+
assert step_second is not None
157161
assert osmo.history.get_step_count(step_first) > 0
158162
assert osmo.history.get_step_count(step_second) > 0

pyproject.toml

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,10 @@ dependencies = [
3030

3131
[project.optional-dependencies]
3232
dev = [
33-
"pytest>=7.0",
34-
"mypy>=1.18.2",
3533
"pytest>=9.0.0",
3634
"hypothesis>=6.0",
3735
"ruff>=0.1.0",
38-
"mypy>=1.0",
36+
"ty>=0.0.17",
3937
]
4038

4139
[project.scripts]
@@ -91,19 +89,8 @@ runner = "python -m pytest pyosmo/tests/"
9189
tests_dir = "pyosmo/tests/"
9290
dict_synonyms = "Struct, NamedStruct"
9391

94-
[tool.mypy]
95-
python_version = "3.11"
96-
warn_return_any = true
97-
warn_unused_configs = true
98-
disallow_untyped_defs = false
99-
disallow_any_generics = false
100-
check_untyped_defs = true
101-
no_implicit_optional = true
102-
warn_redundant_casts = true
103-
warn_unused_ignores = true
104-
warn_no_return = true
105-
strict_equality = true
92+
[tool.ty.environment]
93+
python-version = "3.11"
10694

107-
[[tool.mypy.overrides]]
108-
module = "pyosmo.tests.*"
109-
disallow_untyped_defs = false
95+
[tool.ty.src]
96+
include = ["pyosmo/"]

0 commit comments

Comments
 (0)