From 5e55c0df54f8f6fc3bda632176bb8000758be815 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Mon, 19 Jan 2026 15:50:14 +0200 Subject: [PATCH] replace pyright with ty type checker --- .github/workflows/test.yml | 2 +- .pre-commit-config.yaml | 10 +++-- CHANGELOG.md | 6 +++ app/backend/main.py | 2 +- evaluations/evaluations/datasets/hotpotqa.py | 6 +-- evaluations/evaluations/datasets/repliqa.py | 8 ++-- evaluations/evaluations/datasets/wix.py | 12 +++--- evaluations/evaluations/evaluators/judge.py | 2 +- haiku_rag_slim/haiku/rag/agents/qa/agent.py | 7 ++-- .../haiku/rag/agents/research/graph.py | 13 +++--- haiku_rag_slim/haiku/rag/app.py | 2 +- haiku_rag_slim/haiku/rag/chat/app.py | 2 +- haiku_rag_slim/haiku/rag/inspector/app.py | 2 +- haiku_rag_slim/haiku/rag/store/engine.py | 4 +- haiku_rag_slim/haiku/rag/utils.py | 4 +- pyproject.toml | 12 +++--- tests/agents/chat/test_chat_agent.py | 2 +- tests/conftest.py | 6 +-- tests/test_client.py | 2 +- tests/test_mcp.py | 32 +++++--------- tests/test_rebuild.py | 2 +- tests/test_search.py | 4 +- tests/test_versioning.py | 10 ++--- uv.lock | 42 ++++++++++++------- 24 files changed, 104 insertions(+), 90 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 79a9ed52..eac409f0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,7 @@ jobs: - name: Lint run: uv run ruff check - name: Type check - run: uv run pyright + run: uv run ty check lint-frontend: runs-on: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 46a4d4a1..8261b617 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,10 +16,14 @@ repos: # Run the formatter. - id: ruff-format - - repo: https://github.com/RobertCraigie/pyright-python - rev: v1.1.407 + - repo: local hooks: - - id: pyright + - id: ty + name: ty check + entry: uvx ty check + language: system + types: [python] + pass_filenames: false - repo: local hooks: diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d056a5c..393fa96c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Changelog ## [Unreleased] +### Changed + +- **Type Checker**: Replaced pyright with [ty](https://github.com/astral-sh/ty), Astral's extremely fast Python type checker + - Added explicit `Agent[Deps, Output]` type annotations to all pydantic-ai agents for better type inference + - Removed ~24 unnecessary `# type: ignore` comments that ty correctly infers + ## [0.26.6] - 2026-01-19 ### Changed diff --git a/app/backend/main.py b/app/backend/main.py index e8c4309a..6288f75e 100644 --- a/app/backend/main.py +++ b/app/backend/main.py @@ -226,7 +226,7 @@ app = Starlette( ], middleware=[ Middleware( - CORSMiddleware, + CORSMiddleware, # type: ignore[invalid-argument-type] allow_origins=["http://localhost:3000", "http://frontend:3000"], allow_credentials=True, allow_methods=["*"], diff --git a/evaluations/evaluations/datasets/hotpotqa.py b/evaluations/evaluations/datasets/hotpotqa.py index 2f0a9b1c..d47f939e 100644 --- a/evaluations/evaluations/datasets/hotpotqa.py +++ b/evaluations/evaluations/datasets/hotpotqa.py @@ -1,7 +1,7 @@ from collections.abc import Mapping from typing import Any, cast -from datasets import Dataset, DatasetDict, load_dataset +from datasets import Dataset, load_dataset from pydantic_evals import Case from evaluations.config import DatasetSpec, DocumentPayload, RetrievalSample @@ -9,8 +9,8 @@ from evaluations.evaluators import MAPEvaluator def load_hotpotqa_validation() -> Dataset: - dataset_dict = cast(DatasetDict, load_dataset("hotpotqa/hotpot_qa", "distractor")) - return cast(Dataset, dataset_dict["validation"]) + dataset_dict = load_dataset("hotpotqa/hotpot_qa", "distractor") + return dataset_dict["validation"] def extract_unique_documents(dataset: Dataset) -> list[dict[str, Any]]: diff --git a/evaluations/evaluations/datasets/repliqa.py b/evaluations/evaluations/datasets/repliqa.py index 16ef964e..ba6a1ec8 100644 --- a/evaluations/evaluations/datasets/repliqa.py +++ b/evaluations/evaluations/datasets/repliqa.py @@ -1,7 +1,7 @@ from collections.abc import Mapping -from typing import Any, cast +from typing import Any -from datasets import Dataset, DatasetDict, load_dataset +from datasets import Dataset, load_dataset from pydantic_evals import Case from evaluations.config import DatasetSpec, DocumentPayload, RetrievalSample @@ -9,8 +9,8 @@ from evaluations.evaluators import MRREvaluator def load_repliqa_corpus() -> Dataset: - dataset_dict = cast(DatasetDict, load_dataset("ServiceNow/repliqa")) - dataset = cast(Dataset, dataset_dict["repliqa_3"]) + dataset_dict = load_dataset("ServiceNow/repliqa") + dataset = dataset_dict["repliqa_3"] return dataset.filter(lambda doc: doc["document_topic"] == "News Stories") diff --git a/evaluations/evaluations/datasets/wix.py b/evaluations/evaluations/datasets/wix.py index c6f01c3f..7e81fb8f 100644 --- a/evaluations/evaluations/datasets/wix.py +++ b/evaluations/evaluations/datasets/wix.py @@ -1,8 +1,8 @@ import json from collections.abc import Iterable, Mapping -from typing import Any, cast +from typing import Any -from datasets import Dataset, DatasetDict, load_dataset +from datasets import Dataset, load_dataset from pydantic_evals import Case from evaluations.config import DatasetSpec, DocumentPayload, RetrievalSample @@ -10,8 +10,8 @@ from evaluations.evaluators import MAPEvaluator def load_wix_corpus() -> Dataset: - dataset_dict = cast(DatasetDict, load_dataset("Wix/WixQA", "wix_kb_corpus")) - return cast(Dataset, dataset_dict["train"]) + dataset_dict = load_dataset("Wix/WixQA", "wix_kb_corpus") + return dataset_dict["train"] def map_wix_document(doc: Mapping[str, Any]) -> DocumentPayload: @@ -35,8 +35,8 @@ def map_wix_document(doc: Mapping[str, Any]) -> DocumentPayload: def load_wix_qa() -> Dataset: - dataset_dict = cast(DatasetDict, load_dataset("Wix/WixQA", "wixqa_expertwritten")) - return cast(Dataset, dataset_dict["train"]) + dataset_dict = load_dataset("Wix/WixQA", "wixqa_expertwritten") + return dataset_dict["train"] def map_wix_retrieval(doc: Mapping[str, Any]) -> RetrievalSample | None: diff --git a/evaluations/evaluations/evaluators/judge.py b/evaluations/evaluations/evaluators/judge.py index e2b020ad..df471484 100644 --- a/evaluations/evaluations/evaluators/judge.py +++ b/evaluations/evaluations/evaluators/judge.py @@ -40,7 +40,7 @@ class LLMJudge: model_obj = get_model(model_config, config) # Create Pydantic AI agent - self._agent = Agent( + self._agent: Agent[None, LLMJudgeResponseSchema] = Agent( model=model_obj, output_type=LLMJudgeResponseSchema, system_prompt=ANSWER_EQUIVALENCE_RUBRIC, diff --git a/haiku_rag_slim/haiku/rag/agents/qa/agent.py b/haiku_rag_slim/haiku/rag/agents/qa/agent.py index d2b5194b..18be8a77 100644 --- a/haiku_rag_slim/haiku/rag/agents/qa/agent.py +++ b/haiku_rag_slim/haiku/rag/agents/qa/agent.py @@ -32,7 +32,7 @@ class QuestionAnswerAgent: self._client = client model_obj = get_model(model_config, config) - self._agent = Agent( + self._agent: Agent[Dependencies, RawSearchAnswer] = Agent( model=model_obj, deps_type=Dependencies, output_type=ToolOutput(RawSearchAnswer, max_retries=3), @@ -75,5 +75,6 @@ class QuestionAnswerAgent: """ deps = Dependencies(client=self._client, search_filter=filter) result = await self._agent.run(question, deps=deps) - citations = resolve_citations(result.output.cited_chunks, deps.search_results) - return result.output.answer, citations + output = result.output + citations = resolve_citations(output.cited_chunks, deps.search_results) + return output.answer, citations diff --git a/haiku_rag_slim/haiku/rag/agents/research/graph.py b/haiku_rag_slim/haiku/rag/agents/research/graph.py index 8023ba52..13e958f9 100644 --- a/haiku_rag_slim/haiku/rag/agents/research/graph.py +++ b/haiku_rag_slim/haiku/rag/agents/research/graph.py @@ -105,7 +105,7 @@ async def _plan_step_logic( else plan_prompt ) - plan_agent = Agent( + plan_agent: Agent[ResearchDependencies, ResearchPlan] = Agent( model=get_model(model_config, config), output_type=ResearchPlan, instructions=effective_plan_prompt, @@ -151,7 +151,8 @@ async def _plan_step_logic( agent_deps = ResearchDependencies(client=deps.client, context=state.context) plan_result = await plan_agent.run(prompt, deps=agent_deps) - state.context.sub_questions = list(plan_result.output.sub_questions) + output = plan_result.output + state.context.sub_questions = list(output.sub_questions) async def _search_one_step_logic( @@ -168,7 +169,7 @@ async def _search_one_step_logic( deps.semaphore = asyncio.Semaphore(state.max_concurrency) async with deps.semaphore: - agent = Agent( + agent: Agent[ResearchDependencies, RawSearchAnswer] = Agent( model=get_model(model_config, config), output_type=ToolOutput(RawSearchAnswer, max_retries=3), instructions=search_prompt, @@ -289,7 +290,7 @@ def build_research_graph( state = ctx.state deps = ctx.deps - agent = Agent( + agent: Agent[ResearchDependencies, EvaluationResult] = Agent( model=get_model(model_config, config), output_type=EvaluationResult, instructions=decision_prompt, @@ -350,7 +351,7 @@ def build_research_graph( state = ctx.state deps = ctx.deps - agent = Agent( + agent: Agent[ResearchDependencies, ResearchReport] = Agent( model=get_model(model_config, config), output_type=ResearchReport, instructions=synthesis_prompt, @@ -488,7 +489,7 @@ def build_conversational_graph( state = ctx.state deps = ctx.deps - agent = Agent( + agent: Agent[ResearchDependencies, ConversationalAnswer] = Agent( model=get_model(config.research.model, config), output_type=ConversationalAnswer, instructions=conversational_prompt, diff --git a/haiku_rag_slim/haiku/rag/app.py b/haiku_rag_slim/haiku/rag/app.py index 48a31a44..ece96e40 100644 --- a/haiku_rag_slim/haiku/rag/app.py +++ b/haiku_rag_slim/haiku/rag/app.py @@ -494,7 +494,7 @@ class HaikuRAGApp: # Confidence (from last evaluation) if state.last_eval: - conf = state.last_eval.confidence_score # type: ignore[attr-defined] + conf = state.last_eval.confidence_score self.console.print(f"[bold cyan]Confidence:[/bold cyan] {conf:.1%}") self.console.print() diff --git a/haiku_rag_slim/haiku/rag/chat/app.py b/haiku_rag_slim/haiku/rag/chat/app.py index 24365fb4..c4fa7f9a 100644 --- a/haiku_rag_slim/haiku/rag/chat/app.py +++ b/haiku_rag_slim/haiku/rag/chat/app.py @@ -51,7 +51,7 @@ except ImportError: App = object # type: ignore -class ChatApp(App): # type: ignore[misc] +class ChatApp(App): """Textual TUI for conversational RAG.""" TITLE = "haiku.rag Chat" diff --git a/haiku_rag_slim/haiku/rag/inspector/app.py b/haiku_rag_slim/haiku/rag/inspector/app.py index fd1b761d..67c45dda 100644 --- a/haiku_rag_slim/haiku/rag/inspector/app.py +++ b/haiku_rag_slim/haiku/rag/inspector/app.py @@ -26,7 +26,7 @@ except ImportError: App = object # type: ignore -class InspectorApp(App): # type: ignore[misc] # pragma: no cover +class InspectorApp(App): # pragma: no cover """Textual TUI for inspecting LanceDB data.""" TITLE = "haiku.rag DB Inspector" diff --git a/haiku_rag_slim/haiku/rag/store/engine.py b/haiku_rag_slim/haiku/rag/store/engine.py index f89acd4e..305271cd 100644 --- a/haiku_rag_slim/haiku/rag/store/engine.py +++ b/haiku_rag_slim/haiku/rag/store/engine.py @@ -258,7 +258,7 @@ class Store: } # Documents table stats - doc_stats: dict = self.documents_table.stats() # type: ignore[assignment] + doc_stats: dict = self.documents_table.stats() stats_dict["documents"] = { "exists": True, "num_rows": doc_stats.get("num_rows", 0), @@ -266,7 +266,7 @@ class Store: } # Chunks table stats - chunk_stats: dict = self.chunks_table.stats() # type: ignore[assignment] + chunk_stats: dict = self.chunks_table.stats() stats_dict["chunks"] = { "exists": True, "num_rows": chunk_stats.get("num_rows", 0), diff --git a/haiku_rag_slim/haiku/rag/utils.py b/haiku_rag_slim/haiku/rag/utils.py index 49424643..f51770f7 100644 --- a/haiku_rag_slim/haiku/rag/utils.py +++ b/haiku_rag_slim/haiku/rag/utils.py @@ -185,11 +185,11 @@ def get_model( if model_config.enable_thinking is not None: if model_config.enable_thinking: anthropic_settings = AnthropicModelSettings( - anthropic_thinking={"type": "enabled", "budget_tokens": 4096} + anthropic_thinking={"type": "enabled", "budget_tokens": 4096} # ty: ignore[invalid-argument-type] ) else: anthropic_settings = AnthropicModelSettings( - anthropic_thinking={"type": "disabled"} + anthropic_thinking={"type": "disabled"} # ty: ignore[invalid-argument-type] ) anthropic_settings = apply_common_settings( diff --git a/pyproject.toml b/pyproject.toml index 816ddf80..c107078e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,7 +75,7 @@ dev = [ "pydantic-ai-slim[bedrock]", "pydantic-ai-slim[google]", "pydantic-ai-slim[groq]", - "pyright>=1.1.408", + "ty>=0.0.1a17", "pytest>=9.0.2", "pytest-asyncio>=1.3.0", "pytest-cov>=7.0.0", @@ -110,10 +110,12 @@ indent-style = "space" skip-magic-trailing-comma = false line-ending = "auto" -[tool.pyright] -venvPath = "." -venv = ".venv" -pythonVersion = "3.12" +[tool.ty.src] +exclude = ["examples/**"] + +[tool.ty.environment] +python-version = "3.12" +python = ".venv" [tool.pytest.ini_options] asyncio_default_fixture_loop_scope = "session" diff --git a/tests/agents/chat/test_chat_agent.py b/tests/agents/chat/test_chat_agent.py index 2592002c..0dba3637 100644 --- a/tests/agents/chat/test_chat_agent.py +++ b/tests/agents/chat/test_chat_agent.py @@ -83,7 +83,7 @@ def test_chat_agent_has_dynamic_system_prompt(): # (the add_background_context function) assert len(agent._system_prompt_functions) >= 1 # Verify it's the add_background_context function - func_names = [r.function.__name__ for r in agent._system_prompt_functions] + func_names = [r.function.__name__ for r in agent._system_prompt_functions] # ty: ignore[unresolved-attribute] assert "add_background_context" in func_names diff --git a/tests/conftest.py b/tests/conftest.py index 796745bd..c92956b4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -26,7 +26,7 @@ from datasets import Dataset, load_dataset, load_from_disk # noqa: E402 if TYPE_CHECKING: from vcr import VCR -pydantic_ai.models.ALLOW_MODEL_REQUESTS = False +pydantic_ai.models.ALLOW_MODEL_REQUESTS = False # ty: ignore[invalid-assignment] logging.getLogger("vcr.cassette").setLevel(logging.WARNING) @@ -35,10 +35,10 @@ def qa_corpus() -> Dataset: ds_path = Path(__file__).parent / "data" / "dataset" ds_path.mkdir(parents=True, exist_ok=True) try: - ds: Dataset = load_from_disk(ds_path) # type: ignore + ds: Dataset = load_from_disk(ds_path) # ty: ignore[invalid-assignment] return ds except FileNotFoundError: - ds: Dataset = load_dataset("ServiceNow/repliqa")["repliqa_3"] # type: ignore + ds: Dataset = load_dataset("ServiceNow/repliqa")["repliqa_3"] corpus = ds.filter(lambda doc: doc["document_topic"] == "News Stories") corpus.save_to_disk(ds_path) return corpus diff --git a/tests/test_client.py b/tests/test_client.py index b4099636..198c0b30 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -58,7 +58,7 @@ async def test_client_document_crud(qa_corpus: Dataset, temp_db_path): # Test update_document updated_doc = await client.update_document( - document_id=retrieved_doc.id, # type: ignore[arg-type] + document_id=retrieved_doc.id, content="Updated content", ) assert updated_doc.content == "Updated content" diff --git a/tests/test_mcp.py b/tests/test_mcp.py index cae8c528..dee34e18 100644 --- a/tests/test_mcp.py +++ b/tests/test_mcp.py @@ -30,8 +30,7 @@ async def test_mcp_add_document_from_file(): t for t in tools.values() if t.name == "add_document_from_file" ) - result = await add_file_tool.fn(file_path="/test.txt") # type: ignore[attr-defined] - + result = await add_file_tool.fn(file_path="/test.txt") assert result == "doc123" mock_rag.create_document_from_source.assert_called_once() @@ -52,9 +51,7 @@ async def test_mcp_ask_question(): tools = await mcp.get_tools() ask_tool = next(t for t in tools.values() if t.name == "ask_question") - result = await ask_tool.fn( # type: ignore[attr-defined] - question="What is this?", cite=False, deep=False - ) + result = await ask_tool.fn(question="What is this?", cite=False, deep=False) assert result == "This is the answer" mock_rag.ask.assert_called_once_with("What is this?") @@ -81,8 +78,7 @@ async def test_mcp_add_document_from_url(): t for t in tools.values() if t.name == "add_document_from_url" ) - result = await add_url_tool.fn(url="https://example.com") # type: ignore[attr-defined] - + result = await add_url_tool.fn(url="https://example.com") assert result == "doc456" mock_rag.create_document_from_source.assert_called_once() @@ -108,9 +104,7 @@ async def test_mcp_add_document_from_text(): t for t in tools.values() if t.name == "add_document_from_text" ) - result = await add_text_tool.fn( # type: ignore[attr-defined] - content="test content", uri="text://test" - ) + result = await add_text_tool.fn(content="test content", uri="text://test") assert result == "doc789" mock_rag.create_document.assert_called_once() @@ -141,8 +135,7 @@ async def test_mcp_search_documents(): t for t in tools.values() if t.name == "search_documents" ) - result = await search_tool.fn(query="test query", limit=5) # type: ignore[attr-defined] - + result = await search_tool.fn(query="test query", limit=5) assert len(result) == 2 assert result[0].document_id == "doc1" assert result[0].content == "Result 1" @@ -174,8 +167,7 @@ async def test_mcp_get_document(): tools = await mcp.get_tools() get_tool = next(t for t in tools.values() if t.name == "get_document") - result = await get_tool.fn(document_id="doc123") # type: ignore[attr-defined] - + result = await get_tool.fn(document_id="doc123") assert result is not None assert result.id == "doc123" assert result.content == "test" @@ -211,8 +203,7 @@ async def test_mcp_list_documents(): tools = await mcp.get_tools() list_tool = next(t for t in tools.values() if t.name == "list_documents") - result = await list_tool.fn(limit=10, offset=0) # type: ignore[attr-defined] - + result = await list_tool.fn(limit=10, offset=0) assert len(result) == 2 assert result[0].id == "doc1" assert result[1].id == "doc2" @@ -235,8 +226,7 @@ async def test_mcp_delete_document(): tools = await mcp.get_tools() delete_tool = next(t for t in tools.values() if t.name == "delete_document") - result = await delete_tool.fn(document_id="doc123") # type: ignore[attr-defined] - + result = await delete_tool.fn(document_id="doc123") assert result is True mock_rag.delete_document.assert_called_once_with("doc123") @@ -268,9 +258,7 @@ async def test_mcp_ask_question_deep(): ask_tool = next(t for t in tools.values() if t.name == "ask_question") # cite=False to avoid citation formatting in output - result = await ask_tool.fn( # type: ignore[attr-defined] - question="Deep question?", cite=False, deep=True - ) + result = await ask_tool.fn(question="Deep question?", cite=False, deep=True) assert result == "Deep answer from research" mock_graph.run.assert_called_once() @@ -311,7 +299,7 @@ async def test_mcp_research_question(): t for t in tools.values() if t.name == "research_question" ) - result = await research_tool.fn( # type: ignore[attr-defined] + result = await research_tool.fn( question="Research question?", ) diff --git a/tests/test_rebuild.py b/tests/test_rebuild.py index 2e33e962..6dfa0b90 100644 --- a/tests/test_rebuild.py +++ b/tests/test_rebuild.py @@ -167,7 +167,7 @@ async def test_rebuild_embed_only_with_changed_vector_dim( content=c["content"], content_fts=c["content_fts"], metadata=c["metadata"], - order=c["order"], + order=c["order"], # ty: ignore[invalid-argument-type] vector=[0.1] * 4096, ) for c in chunk_data diff --git a/tests/test_search.py b/tests/test_search.py index ff81b4da..c302d25e 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -19,8 +19,8 @@ async def test_search_qa_corpus(qa_corpus: Dataset, temp_db_path): for doc_data in qa_corpus: if len(seen_documents) >= 10: break - document_text = doc_data["document_extracted"] # type: ignore - document_id = doc_data.get("document_id", "") # type: ignore + document_text = doc_data["document_extracted"] + document_id = doc_data.get("document_id", "") if document_id in seen_documents: continue diff --git a/tests/test_versioning.py b/tests/test_versioning.py index 2a302a61..2e7be9f6 100644 --- a/tests/test_versioning.py +++ b/tests/test_versioning.py @@ -14,7 +14,7 @@ async def test_version_rollback_on_create_failure(temp_db_path): await orig_create(chunks) raise RuntimeError("boom") - client.chunk_repository.create = succeed_then_fail # type: ignore[method-assign] + client.chunk_repository.create = succeed_then_fail # Attempt to create document; expect failure and rollback content = "Hello, rollback!" @@ -43,20 +43,20 @@ async def test_version_rollback_on_update_failure(temp_db_path): await orig_create(chunks) raise RuntimeError("update fail") - client.chunk_repository.create = succeed_then_fail # type: ignore[method-assign] + client.chunk_repository.create = succeed_then_fail # Attempt update with pytest.raises(RuntimeError): await client.update_document( - document_id=created.id, # type: ignore[arg-type] + document_id=created.id, content="Updated content", ) # Content and chunks should remain the original - persisted = await client.get_document_by_id(created.id) # type: ignore[arg-type] + persisted = await client.get_document_by_id(created.id) assert persisted is not None assert persisted.content == base_content - original_chunks = await client.chunk_repository.get_by_document_id(created.id) # type: ignore[arg-type] + original_chunks = await client.chunk_repository.get_by_document_id(created.id) assert len(original_chunks) > 0 diff --git a/uv.lock b/uv.lock index 79879e9e..c94f642e 100644 --- a/uv.lock +++ b/uv.lock @@ -1304,12 +1304,12 @@ dev = [ { name = "mkdocs-material" }, { name = "pre-commit" }, { name = "pydantic-ai-slim", extra = ["anthropic", "bedrock", "google", "groq"] }, - { name = "pyright" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, { name = "pytest-recording" }, { name = "ruff" }, + { name = "ty" }, ] [package.metadata] @@ -1330,12 +1330,12 @@ dev = [ { name = "pydantic-ai-slim", extras = ["bedrock"] }, { name = "pydantic-ai-slim", extras = ["google"] }, { name = "pydantic-ai-slim", extras = ["groq"] }, - { name = "pyright", specifier = ">=1.1.408" }, { name = "pytest", specifier = ">=9.0.2" }, { name = "pytest-asyncio", specifier = ">=1.3.0" }, { name = "pytest-cov", specifier = ">=7.0.0" }, { name = "pytest-recording", specifier = ">=0.13.4" }, { name = "ruff", specifier = ">=0.14.11" }, + { name = "ty", specifier = ">=0.0.1a17" }, ] [[package]] @@ -3843,19 +3843,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" }, ] -[[package]] -name = "pyright" -version = "1.1.408" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nodeenv" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/74/b2/5db700e52554b8f025faa9c3c624c59f1f6c8841ba81ab97641b54322f16/pyright-1.1.408.tar.gz", hash = "sha256:f28f2321f96852fa50b5829ea492f6adb0e6954568d1caa3f3af3a5f555eb684", size = 4400578, upload-time = "2026-01-08T08:07:38.795Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/82/a2c93e32800940d9573fb28c346772a14778b84ba7524e691b324620ab89/pyright-1.1.408-py3-none-any.whl", hash = "sha256:090b32865f4fdb1e0e6cd82bf5618480d48eecd2eb2e70f960982a3d9a4c17c1", size = 6399144, upload-time = "2026-01-08T08:07:37.082Z" }, -] - [[package]] name = "pytest" version = "9.0.2" @@ -4992,6 +4979,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/b5/b0d3d8b901b6a04ca38df5e24c27e53afb15b93624d7fd7d658c7cd9352a/triton-3.5.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bac7f7d959ad0f48c0e97d6643a1cc0fd5786fe61cb1f83b537c6b2d54776478", size = 170582192, upload-time = "2025-11-11T17:41:23.963Z" }, ] +[[package]] +name = "ty" +version = "0.0.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/78/ba1a4ad403c748fbba8be63b7e774a90e80b67192f6443d624c64fe4aaab/ty-0.0.12.tar.gz", hash = "sha256:cd01810e106c3b652a01b8f784dd21741de9fdc47bd595d02c122a7d5cefeee7", size = 4981303, upload-time = "2026-01-14T22:30:48.537Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/8f/c21314d074dda5fb13d3300fa6733fd0d8ff23ea83a721818740665b6314/ty-0.0.12-py3-none-linux_armv6l.whl", hash = "sha256:eb9da1e2c68bd754e090eab39ed65edf95168d36cbeb43ff2bd9f86b4edd56d1", size = 9614164, upload-time = "2026-01-14T22:30:44.016Z" }, + { url = "https://files.pythonhosted.org/packages/09/28/f8a4d944d13519d70c486e8f96d6fa95647ac2aa94432e97d5cfec1f42f6/ty-0.0.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c181f42aa19b0ed7f1b0c2d559980b1f1d77cc09419f51c8321c7ddf67758853", size = 9542337, upload-time = "2026-01-14T22:30:05.687Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9c/f576e360441de7a8201daa6dc4ebc362853bc5305e059cceeb02ebdd9a48/ty-0.0.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1f829e1eecd39c3e1b032149db7ae6a3284f72fc36b42436e65243a9ed1173db", size = 8909582, upload-time = "2026-01-14T22:30:46.089Z" }, + { url = "https://files.pythonhosted.org/packages/d6/13/0898e494032a5d8af3060733d12929e3e7716db6c75eac63fa125730a3e7/ty-0.0.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45162e7826e1789cf3374627883cdeb0d56b82473a0771923e4572928e90be3", size = 9384932, upload-time = "2026-01-14T22:30:13.769Z" }, + { url = "https://files.pythonhosted.org/packages/e4/1a/b35b6c697008a11d4cedfd34d9672db2f0a0621ec80ece109e13fca4dfef/ty-0.0.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d11fec40b269bec01e751b2337d1c7ffa959a2c2090a950d7e21c2792442cccd", size = 9453140, upload-time = "2026-01-14T22:30:11.131Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/71c9edbc79a3c88a0711324458f29c7dbf6c23452c6e760dc25725483064/ty-0.0.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09d99e37e761a4d2651ad9d5a610d11235fbcbf35dc6d4bc04abf54e7cf894f1", size = 9960680, upload-time = "2026-01-14T22:30:33.621Z" }, + { url = "https://files.pythonhosted.org/packages/0e/75/39375129f62dd22f6ad5a99cd2a42fd27d8b91b235ce2db86875cdad397d/ty-0.0.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d9ca0cdb17bd37397da7b16a7cd23423fc65c3f9691e453ad46c723d121225a1", size = 10904518, upload-time = "2026-01-14T22:30:08.464Z" }, + { url = "https://files.pythonhosted.org/packages/32/5e/26c6d88fafa11a9d31ca9f4d12989f57782ec61e7291d4802d685b5be118/ty-0.0.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcf2757b905e7eddb7e456140066335b18eb68b634a9f72d6f54a427ab042c64", size = 10525001, upload-time = "2026-01-14T22:30:16.454Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a5/2f0b91894af13187110f9ad7ee926d86e4e6efa755c9c88a820ed7f84c85/ty-0.0.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00cf34c1ebe1147efeda3021a1064baa222c18cdac114b7b050bbe42deb4ca80", size = 10307103, upload-time = "2026-01-14T22:30:41.221Z" }, + { url = "https://files.pythonhosted.org/packages/4b/77/13d0410827e4bc713ebb7fdaf6b3590b37dcb1b82e0a81717b65548f2442/ty-0.0.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb3a655bd869352e9a22938d707631ac9fbca1016242b1f6d132d78f347c851", size = 10072737, upload-time = "2026-01-14T22:30:51.783Z" }, + { url = "https://files.pythonhosted.org/packages/e1/dd/fc36d8bac806c74cf04b4ca735bca14d19967ca84d88f31e121767880df1/ty-0.0.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4658e282c7cb82be304052f8f64f9925f23c3c4f90eeeb32663c74c4b095d7ba", size = 9368726, upload-time = "2026-01-14T22:30:18.683Z" }, + { url = "https://files.pythonhosted.org/packages/54/70/9e8e461647550f83e2fe54bc632ccbdc17a4909644783cdbdd17f7296059/ty-0.0.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c167d838eaaa06e03bb66a517f75296b643d950fbd93c1d1686a187e5a8dbd1f", size = 9454704, upload-time = "2026-01-14T22:30:22.759Z" }, + { url = "https://files.pythonhosted.org/packages/04/9b/6292cf7c14a0efeca0539cf7d78f453beff0475cb039fbea0eb5d07d343d/ty-0.0.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2956e0c9ab7023533b461d8a0e6b2ea7b78e01a8dde0688e8234d0fce10c4c1c", size = 9649829, upload-time = "2026-01-14T22:30:31.234Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/472a5d2013371e4870886cff791c94abdf0b92d43d305dd0f8e06b6ff719/ty-0.0.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5c6a3fd7479580009f21002f3828320621d8a82d53b7ba36993234e3ccad58c8", size = 10162814, upload-time = "2026-01-14T22:30:36.174Z" }, + { url = "https://files.pythonhosted.org/packages/31/e9/2ecbe56826759845a7c21d80aa28187865ea62bc9757b056f6cbc06f78ed/ty-0.0.12-py3-none-win32.whl", hash = "sha256:a91c24fd75c0f1796d8ede9083e2c0ec96f106dbda73a09fe3135e075d31f742", size = 9140115, upload-time = "2026-01-14T22:30:38.903Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6d/d9531eff35a5c0ec9dbc10231fac21f9dd6504814048e81d6ce1c84dc566/ty-0.0.12-py3-none-win_amd64.whl", hash = "sha256:df151894be55c22d47068b0f3b484aff9e638761e2267e115d515fcc9c5b4a4b", size = 9884532, upload-time = "2026-01-14T22:30:25.112Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f3/20b49e75967023b123a221134548ad7000f9429f13fdcdda115b4c26305f/ty-0.0.12-py3-none-win_arm64.whl", hash = "sha256:cea99d334b05629de937ce52f43278acf155d3a316ad6a35356635f886be20ea", size = 9313974, upload-time = "2026-01-14T22:30:27.44Z" }, +] + [[package]] name = "typer" version = "0.19.2"