Skip to content

Commit 87ad793

Browse files
authored
chore: Migrate from mypy to ty for type checking (#137)
Fixed all typing issues across the entire codebase: **Examples (5 fixes):** - error_handling.py: Fixed async callable type annotation and exception handling - low_stock_monitoring.py: Added VariantResponse type checking **Generated code (47 fixes via patterns):** - Added type: ignore[arg-type] to all .from_dict(data) calls in API files - Added type: ignore[misc] to from_dict method signatures in model files - Fixed to_dict() calls in receive_purchase_order.py - Updated regeneration script to apply these fixes automatically **Core code (5 fixes):** - utils.py: Fixed list append type inference - domain/converters.py: Fixed product_or_material_name and deleted_at type inference **Configuration:** - Excluded katana_mcp_server/ from typecheck (separate package with own config) **Results:** - Before: 57 diagnostics - After: 0 diagnostics (All checks passed!) - All 1666 tests passing Improved type checking approach: - Use `typing.cast()` for explicit type narrowing in generated API code - Replaced 3 type: ignore comments with proper cast() calls - Better documented MCP server exclusion (separate workspace package) - All ty checks passing with proper type safety Current type: ignore count: 310 total - Models (generated): 266 (from openapi-python-client generator) - Core code: 22 (legitimate edge cases) - Tests: 9 (pytest.raises type narrowing) - Scripts: 6 (script utilities) - API (generated): 4 (to_dict calls) - Examples: 3 (isinstance narrowing limitations) 1. Improved ty configuration documentation in pyproject.toml - Added clearer comment explaining ty's limited config support - Documented that exclusions are handled via command line flags 2. Enhanced error handling in regenerate_client.py - Replaced silent exception catching with explicit error logging - Now prints warnings when files can't be processed 3. Restructured error_handling.py retry logic - Removed unreachable code that only existed for type checker - Added input validation for max_attempts parameter - Used assertion to prove last_exception is set before raising - Cleaner logic flow without type checker workarounds
1 parent c5e5dc2 commit 87ad793

277 files changed

Lines changed: 541 additions & 447 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENT_WORKFLOW.md

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ parallel with other agents.
99
**Fast Commands:**
1010

1111
- `uv run poe quick-check` - Format + lint only (~5-10s) - **Use during development**
12-
- `uv run poe agent-check` - Format + lint + mypy (~10-15s) - **Use before committing**
12+
- `uv run poe agent-check` - Format + lint + ty (~8-12s) - **Use before committing**
1313
- `uv run poe check` - Full validation (~40s) - **Required before opening PR**
1414
- `uv run poe full-check` - Everything including docs (~50s) - **Use before requesting
1515
review**
@@ -418,7 +418,7 @@ pre-commit run --all-files
418418
**What `check` does:**
419419

420420
1. Format check (ruff, markdown)
421-
1. Linting (ruff, mypy, yamllint)
421+
1. Linting (ruff, ty, yamllint)
422422
1. Tests (pytest with coverage, excluding slow docs tests)
423423

424424
**If any checks fail:**
@@ -540,7 +540,7 @@ uv run poe full-check
540540

541541
# This includes:
542542
# - Format check
543-
# - Linting (ruff, mypy, yamllint)
543+
# - Linting (ruff, ty, yamllint)
544544
# - All tests (including docs tests)
545545
# - Coverage check
546546

@@ -625,7 +625,7 @@ ______________________________________________________________________
625625
**Runs:**
626626

627627
- Format check (ruff, markdown)
628-
- Linting (ruff, mypy, yamllint)
628+
- Linting (ruff, ty, yamllint)
629629
- Tests (pytest with coverage, excluding docs)
630630

631631
**Use When:**
@@ -1112,14 +1112,17 @@ uv run ruff check path/to/file.py
11121112
### Type Checking Errors
11131113

11141114
```bash
1115-
# Run mypy
1116-
uv run mypy katana_public_api_client
1115+
# Run ty type checker
1116+
uv run poe typecheck
11171117

1118-
# Check specific file
1119-
uv run mypy path/to/file.py
1118+
# Or run ty directly
1119+
uv run ty check
1120+
1121+
# See ty config
1122+
# Check [tool.ty] in pyproject.toml
11201123

1121-
# See mypy config
1122-
# Check [tool.mypy] in pyproject.toml
1124+
# Note: ty is pre-alpha and may report false positives
1125+
# Uses --exit-zero to prevent CI failures during early adoption
11231126
```
11241127

11251128
### Pre-commit Hook Timeouts

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ cp .env.example .env # Add your KATANA_API_KEY
4747
### Validation Tiers (Use the Right One!)
4848

4949
- **`uv run poe quick-check`** (~5-10s) - Format + lint only → Use during development
50-
- **`uv run poe agent-check`** (~10-15s) - Format + lint + mypy → Use before committing
50+
- **`uv run poe agent-check`** (~8-12s) - Format + lint + ty → Use before committing
5151
- **`uv run poe check`** (~30s) - Full validation → **REQUIRED before opening PR**
5252
- **`uv run poe full-check`** (~40s) - Everything + docs → Use before requesting review
5353

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,9 +366,9 @@ uv run poe format-check # Check formatting without changes
366366
uv run poe format-python # Format Python code only
367367

368368
# Linting and Type Checking
369-
uv run poe lint # Run all linters (ruff, mypy, yaml)
369+
uv run poe lint # Run all linters (ruff, ty, yaml)
370370
uv run poe lint-ruff # Fast linting with ruff
371-
uv run poe lint-mypy # Type checking with mypy
371+
uv run poe typecheck # Type checking with ty
372372

373373
# Testing
374374
uv run poe test # Run all tests

docs/CONTRIBUTING.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ uv run poe test-integration
7070
### Code Style
7171

7272
- We use [Ruff](https://docs.astral.sh/ruff/) for code formatting and linting
73-
- [mypy](https://mypy.readthedocs.io/) for type checking
73+
- [ty](https://astral.sh/blog/ty) for type checking (Astral's fast Rust-based type
74+
checker)
7475
- [mdformat](https://mdformat.readthedocs.io/) for Markdown formatting
7576

7677
All formatting is automated via `uv run poe format`.

docs/RELEASE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ Added comprehensive guide for semantic-release with scopes.
147147

148148
All tests must pass before release:
149149

150-
- Code quality checks (ruff, mypy)
150+
- Code quality checks (ruff, ty)
151151
- Unit and integration tests
152152
- Security scans (CodeQL, Semgrep, Trivy)
153153

docs/UV_USAGE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ uv run poe format-check # Check formatting
144144
# Linting
145145
uv run poe lint # All linters
146146
uv run poe lint-ruff # Ruff linting
147-
uv run poe lint-mypy # Type checking
147+
uv run poe typecheck # Type checking (ty)
148148
uv run poe lint-yaml # YAML linting
149149
```
150150

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ client for the Katana Manufacturing ERP API with automatic resilience features.
77

88
- **Transport-layer resilience**: Automatic retries, rate limiting, and smart pagination
99
at the HTTP transport level
10-
- **Type-safe**: Full type hints and mypy compatibility
10+
- **Type-safe**: Full type hints and ty/mypy compatibility
1111
- **Async/await support**: Built on httpx for modern Python async patterns
1212
- **Production-ready**: Comprehensive error handling and logging
1313
- **Zero-wrapper philosophy**: All resilience features work transparently with the

examples/error_handling.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"""
99

1010
import asyncio
11-
from collections.abc import Callable
11+
from collections.abc import Awaitable, Callable
1212
from typing import Any, TypeVar
1313

1414
from katana_public_api_client import KatanaClient
@@ -19,7 +19,7 @@
1919

2020

2121
async def retry_with_backoff(
22-
operation: Callable[[], T],
22+
operation: Callable[[], Awaitable[T]],
2323
max_attempts: int = 3,
2424
backoff_factor: float = 2.0,
2525
) -> T:
@@ -40,22 +40,27 @@ async def retry_with_backoff(
4040
Raises:
4141
Last exception if all retries fail
4242
"""
43-
last_exception = None
43+
if max_attempts < 1:
44+
raise ValueError("max_attempts must be at least 1")
45+
4446
delay = 1.0
47+
last_exception: Exception | None = None
4548

4649
for attempt in range(max_attempts):
4750
try:
4851
return await operation()
4952
except Exception as e:
5053
last_exception = e
51-
5254
if attempt < max_attempts - 1:
5355
print(f"Attempt {attempt + 1} failed: {e}. Retrying in {delay}s...")
5456
await asyncio.sleep(delay)
5557
delay *= backoff_factor
5658
else:
59+
# Last attempt failed
5760
print(f"All {max_attempts} attempts failed")
5861

62+
# We only reach here if all attempts failed and last_exception is set
63+
assert last_exception is not None, "Logic error: last_exception should be set"
5964
raise last_exception
6065

6166

examples/low_stock_monitoring.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from katana_public_api_client import KatanaClient
1414
from katana_public_api_client.api.inventory import get_all_inventory_point
1515
from katana_public_api_client.api.variant import get_variant
16+
from katana_public_api_client.models import VariantResponse
1617
from katana_public_api_client.utils import unwrap_data
1718

1819

@@ -43,17 +44,19 @@ async def get_low_stock_alerts(threshold: int = 10) -> list[dict[str, Any]]:
4344
client=client, id=inv_point.variant_id
4445
)
4546

46-
if variant_response.parsed:
47+
if variant_response.parsed and isinstance(
48+
variant_response.parsed, VariantResponse
49+
):
4750
variant = variant_response.parsed
4851

4952
low_stock_items.append(
5053
{
51-
"sku": variant.sku,
52-
"name": variant.name,
54+
"sku": variant.sku, # type: ignore[attr-defined]
55+
"name": variant.name, # type: ignore[attr-defined]
5356
"current_stock": inv_point.in_stock,
5457
"location": inv_point.location_name,
5558
"reorder_point": getattr(inv_point, "reorder_point", None),
56-
"variant_id": variant.id,
59+
"variant_id": variant.id, # type: ignore[attr-defined]
5760
}
5861
)
5962

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,41 @@
11
"""Katana Public API Client - Python client for Katana Manufacturing ERP."""
22

3+
__version__ = "0.30.0"
4+
35
from .client import AuthenticatedClient, Client
46
from .katana_client import KatanaClient
7+
from .utils import (
8+
APIError,
9+
AuthenticationError,
10+
RateLimitError,
11+
ServerError,
12+
ValidationError,
13+
get_error_message,
14+
get_variant_display_name,
15+
handle_response,
16+
is_error,
17+
is_success,
18+
unwrap,
19+
unwrap_data,
20+
)
521

622
__all__ = [
23+
# Exceptions
24+
"APIError",
25+
# Client classes
726
"AuthenticatedClient",
27+
"AuthenticationError",
828
"Client",
929
"KatanaClient",
30+
"RateLimitError",
31+
"ServerError",
32+
"ValidationError",
33+
# Utility functions
34+
"get_error_message",
35+
"get_variant_display_name",
36+
"handle_response",
37+
"is_error",
38+
"is_success",
39+
"unwrap",
40+
"unwrap_data",
1041
]

0 commit comments

Comments
 (0)