diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index db71dd183..d18229a63 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -37,7 +37,7 @@ jobs: run: uv run python3 -m pytest . - name: Check Python Types - run: uv run pyright . + run: uvx ty check - name: Build Core run: uv build diff --git a/.github/workflows/format_and_lint.yml b/.github/workflows/format_and_lint.yml index ab77e6a74..e01eb7ee3 100644 --- a/.github/workflows/format_and_lint.yml +++ b/.github/workflows/format_and_lint.yml @@ -34,11 +34,15 @@ jobs: run: uv python install - name: Install the project - run: uv tool install ruff + run: uv sync - name: Lint with ruff run: | uvx ruff check --select I . + - name: Format with ruff run: | uvx ruff format --check . + + - name: Type Check with Ty + run: uvx ty check diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..f7e1cd7a7 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,22 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "astral-sh.ty", + "charliermarsh.ruff", + "esbenp.prettier-vscode", + "ms-python.debugpy", + "svelte.svelte-vscode", + "vitest.explorer", + "dbaeumer.vscode-eslint", + "ms-python.python" + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [ + // We've moved to ty and ruff! They are much faster than pylance/basedpyright + "anysphere.cursorpyright", + "ms-python.vscode-pylance" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index b3ffd1937..2bc917f6c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,10 +15,7 @@ ], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, - "python.analysis.typeCheckingMode": "basic", - "python.analysis.autoImportCompletions": true, - "python.analysis.extraPaths": ["./"], - "python.analysis.ignore": ["**/test_*.py"], + "python.languageServer": "None", "files.exclude": { "**/__pycache__": true, "**/.pytest_cache": true, diff --git a/AGENTS.md b/AGENTS.md index b30d3236d..41ad205c1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,7 @@ uvx ruff check --select I # Formatting 2: uvx ruff format --check . # type checking: warnings in output are acceptable, but error codes are not -uv run pyright . +uvx ty check # tests: uv run python3 -m pytest --benchmark-quiet -q . ``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b512a0b11..1d95b8473 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,13 +61,12 @@ uv run ./checks.sh ### IDE Extensions -We suggest the following extensions for VSCode/Cursor. With them, you'll get compliant formatting and linting in your IDE. +We suggest the following extensions for VSCode/Cursor. With them, you'll get compliant formatting and linting in your IDE. They are also listed in `.vscode/extensions.json` so they will be suggested in the extension store. - Prettier -- Python - Python Debugger -- Type checking by pyright via one of: Cursor Python if using Cursor, Pylance if VSCode -- Ruff +- Ty - language server and type checker for Python +- Ruff - formatter/linter for Python - Svelte for VS Code - Vitest - ESLint diff --git a/README.md b/README.md index b2bc980fc..e8527f881 100644 --- a/README.md +++ b/README.md @@ -17,14 +17,14 @@ Docs

-| | | -| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| | | +| ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | CI | [![Build and Test](https://github.com/Kiln-AI/kiln/actions/workflows/build_and_test.yml/badge.svg)](https://github.com/Kiln-AI/kiln/actions/workflows/build_and_test.yml) [![Format and Lint](https://github.com/Kiln-AI/kiln/actions/workflows/format_and_lint.yml/badge.svg)](https://github.com/Kiln-AI/kiln/actions/workflows/format_and_lint.yml) [![Desktop Apps Build](https://github.com/Kiln-AI/kiln/actions/workflows/build_desktop.yml/badge.svg)](https://github.com/Kiln-AI/kiln/actions/workflows/build_desktop.yml) [![Web UI Build](https://github.com/Kiln-AI/kiln/actions/workflows/web_format_lint_build.yml/badge.svg)](https://github.com/Kiln-AI/kiln/actions/workflows/web_format_lint_build.yml) [![Docs](https://github.com/Kiln-AI/Kiln/actions/workflows/build_docs.yml/badge.svg)](https://github.com/Kiln-AI/Kiln/actions/workflows/build_docs.yml) | -| Tests | [![Test Count Badge](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/scosman/57742c1b1b60d597a6aba5d5148d728e/raw/test_count_kiln.json)](https://github.com/Kiln-AI/kiln/actions/workflows/test_count.yml) [![Test Coverage Badge](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/scosman/57742c1b1b60d597a6aba5d5148d728e/raw/library_coverage_kiln.json)](https://github.com/Kiln-AI/kiln/actions/workflows/test_count.yml) | -| Package | [![PyPI - Version](https://img.shields.io/pypi/v/kiln-ai.svg?logo=pypi&label=PyPI&logoColor=gold)](https://pypi.org/project/kiln-ai/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/kiln-ai.svg?logo=python&label=Python&logoColor=gold)](https://pypi.org/project/kiln-ai/) | -| Meta | [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv) [![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![types - Pyright](https://img.shields.io/badge/types-pyright-blue.svg)](https://github.com/microsoft/pyright) [![Docs](https://img.shields.io/badge/docs-pdoc-blue)](https://kiln-ai.github.io/Kiln/kiln_core_docs/index.html) | -| Apps | [![MacOS](https://img.shields.io/badge/MacOS-black?logo=apple)](https://getkiln.ai/download) [![Windows](https://img.shields.io/badge/Windows-0067b8.svg?logo=data:image/svg%2bxml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyBmaWxsPSIjZmZmIiB2aWV3Qm94PSIwIDAgMzIgMzIiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE2Ljc0MiAxNi43NDJ2MTQuMjUzaDE0LjI1M3YtMTQuMjUzek0xLjAwNCAxNi43NDJ2MTQuMjUzaDE0LjI1NnYtMTQuMjUzek0xNi43NDIgMS4wMDR2MTQuMjU2aDE0LjI1M3YtMTQuMjU2ek0xLjAwNCAxLjAwNHYxNC4yNTZoMTQuMjU2di0xNC4yNTZ6Ij48L3BhdGg+Cjwvc3ZnPg==)](https://getkiln.ai/download) [![Linux](https://img.shields.io/badge/Linux-444444?logo=linux&logoColor=ffffff)](https://getkiln.ai/download) ![Github Downsloads](https://img.shields.io/github/downloads/kiln-ai/kiln/total) | -| Connect | [![Discord](https://img.shields.io/badge/Discord-Kiln_AI-blue?logo=Discord&logoColor=white)](https://getkiln.ai/discord) [![Newsletter](https://img.shields.io/badge/Newsletter-subscribe-blue?logo=mailboxdotorg&logoColor=white)](https://getkiln.ai/blog) | +| Tests | [![Test Count Badge](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/scosman/57742c1b1b60d597a6aba5d5148d728e/raw/test_count_kiln.json)](https://github.com/Kiln-AI/kiln/actions/workflows/test_count.yml) [![Test Coverage Badge](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/scosman/57742c1b1b60d597a6aba5d5148d728e/raw/library_coverage_kiln.json)](https://github.com/Kiln-AI/kiln/actions/workflows/test_count.yml) | +| Package | [![PyPI - Version](https://img.shields.io/pypi/v/kiln-ai.svg?logo=pypi&label=PyPI&logoColor=gold)](https://pypi.org/project/kiln-ai/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/kiln-ai.svg?logo=python&label=Python&logoColor=gold)](https://pypi.org/project/kiln-ai/) | +| Meta | [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv) [![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![types - ty](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/scosman/8011394107e99730fd39a1dab98c7748/raw/97e757c3ef60a3d32df633ac70d279534d117707/ty_badge.json)](https://github.com/astral-sh/ty) [![Docs](https://img.shields.io/badge/docs-pdoc-blue)](https://kiln-ai.github.io/Kiln/kiln_core_docs/index.html) | +| Apps | [![MacOS](https://img.shields.io/badge/MacOS-black?logo=apple)](https://getkiln.ai/download) [![Windows](https://img.shields.io/badge/Windows-0067b8.svg?logo=data:image/svg%2bxml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyBmaWxsPSIjZmZmIiB2aWV3Qm94PSIwIDAgMzIgMzIiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE2Ljc0MiAxNi43NDJ2MTQuMjUzaDE0LjI1M3YtMTQuMjUzek0xLjAwNCAxNi43NDJ2MTQuMjUzaDE0LjI1NnYtMTQuMjUzek0xNi43NDIgMS4wMDR2MTQuMjU2aDE0LjI1M3YtMTQuMjU2ek0xLjAwNCAxLjAwNHYxNC4yNTZoMTQuMjU2di0xNC4yNTZ6Ij48L3BhdGg+Cjwvc3ZnPg==)](https://getkiln.ai/download) [![Linux](https://img.shields.io/badge/Linux-444444?logo=linux&logoColor=ffffff)](https://getkiln.ai/download) ![Github Downsloads](https://img.shields.io/github/downloads/kiln-ai/kiln/total) | +| Connect | [![Discord](https://img.shields.io/badge/Discord-Kiln_AI-blue?logo=Discord&logoColor=white)](https://getkiln.ai/discord) [![Newsletter](https://img.shields.io/badge/Newsletter-subscribe-blue?logo=mailboxdotorg&logoColor=white)](https://getkiln.ai/blog) | [Download button](https://getkiln.ai/download) [Quick start button](https://docs.getkiln.ai/getting-started/quickstart) diff --git a/app/desktop/studio_server/finetune_api.py b/app/desktop/studio_server/finetune_api.py index 2821c0445..2cbc021c3 100644 --- a/app/desktop/studio_server/finetune_api.py +++ b/app/desktop/studio_server/finetune_api.py @@ -5,7 +5,11 @@ import httpx from fastapi import FastAPI, HTTPException from fastapi.responses import StreamingResponse -from kiln_ai.adapters.fine_tune.base_finetune import FineTuneParameter, FineTuneStatus +from kiln_ai.adapters.fine_tune.base_finetune import ( + BaseFinetuneAdapter, + FineTuneParameter, + FineTuneStatus, +) from kiln_ai.adapters.fine_tune.dataset_formatter import ( DatasetFormat, DatasetFormatter, @@ -50,6 +54,19 @@ logger = logging.getLogger(__name__) +def base_provider_from_str_id(provider_str: str) -> type[BaseFinetuneAdapter]: + """ + Validates that a provider string is a valid model provider, throwing an Http error if not. + """ + if provider_str not in finetune_registry: # type: ignore + valid_providers = list(finetune_registry.keys()) + raise HTTPException( + status_code=400, + detail=f"Invalid provider '{provider_str}'. Valid providers are: {valid_providers}", + ) + return finetune_registry[provider_str] # type: ignore + + class FinetuneProviderModel(BaseModel): """Finetune provider model: a model a provider supports for fine-tuning""" @@ -208,7 +225,7 @@ async def finetune( status_code=400, detail=f"Fine tune provider '{finetune.provider}' not found", ) - finetune_adapter = finetune_registry[finetune.provider] + finetune_adapter = base_provider_from_str_id(finetune.provider) status = await finetune_adapter(finetune).status() return FinetuneWithStatus(finetune=finetune, status=status) @@ -276,11 +293,7 @@ async def finetune_providers() -> list[FinetuneProvider]: async def finetune_hyperparameters( provider_id: str, ) -> list[FineTuneParameter]: - if provider_id not in finetune_registry: - raise HTTPException( - status_code=400, detail=f"Fine tune provider '{provider_id}' not found" - ) - finetune_adapter_class = finetune_registry[provider_id] + finetune_adapter_class = base_provider_from_str_id(provider_id) return finetune_adapter_class.available_parameters() @app.get("/api/projects/{project_id}/tasks/{task_id}/finetune_dataset_info") @@ -358,7 +371,7 @@ async def create_finetune( status_code=400, detail=f"Fine tune provider '{request.provider}' not found", ) - finetune_adapter_class = finetune_registry[request.provider] + finetune_adapter_class = base_provider_from_str_id(request.provider) dataset = DatasetSplit.from_id_and_parent_path(request.dataset_id, task.path) if dataset is None: diff --git a/app/desktop/studio_server/provider_api.py b/app/desktop/studio_server/provider_api.py index e931cd99f..a45f9f41c 100644 --- a/app/desktop/studio_server/provider_api.py +++ b/app/desktop/studio_server/provider_api.py @@ -24,8 +24,8 @@ from kiln_ai.adapters.provider_tools import provider_name_from_id, provider_warnings from kiln_ai.datamodel.registry import all_projects from kiln_ai.utils.config import Config -from kiln_ai.utils.exhaustive_error import raise_exhaustive_enum_error from pydantic import BaseModel, Field +from typing_extensions import assert_never logger = logging.getLogger(__name__) @@ -45,7 +45,7 @@ async def connect_ollama(custom_ollama_url: str | None = None) -> OllamaConnecti try: base_url = custom_ollama_url or ollama_base_url() tags = requests.get(base_url + "/api/tags", timeout=5).json() - except requests.exceptions.ConnectionError: + except requests.ConnectionError: raise HTTPException( status_code=417, detail="Failed to connect. Ensure Ollama app is running.", @@ -308,7 +308,7 @@ async def connect_api_key(payload: dict): content={"message": "Provider not supported for API keys"}, ) case _: - raise_exhaustive_enum_error(typed_provider) + assert_never(typed_provider) @app.post("/api/provider/disconnect_api_key") async def disconnect_api_key(provider_id: str) -> JSONResponse: @@ -363,8 +363,8 @@ async def disconnect_api_key(provider_id: str) -> JSONResponse: content={"message": "Provider not supported"}, ) case _: - # Raises a pyright error if I miss a case - raise_exhaustive_enum_error(typed_provider_id) + # Raises a type error if I miss a case + assert_never(typed_provider_id) return JSONResponse( status_code=200, @@ -812,15 +812,15 @@ async def connect_bedrock(key_data: dict): ) except Exception as e: # Improve error message if it's a confirmed authentication error - if isinstance(e, litellm.exceptions.AuthenticationError): + if isinstance(e, litellm.AuthenticationError): return JSONResponse( status_code=401, content={ "message": "Failed to connect to Bedrock. Invalid credentials." }, ) - # If it's a bad request, it's a valid key (but the model is fake) - if isinstance(e, litellm.exceptions.BadRequestError): + # It passed the authentication test, but as expected it's a bad request (expected since the model is fake). This means it worked. + if isinstance(e, litellm.BadRequestError): Config.shared().bedrock_access_key = access_key Config.shared().bedrock_secret_key = secret_key return JSONResponse( diff --git a/app/desktop/studio_server/test_finetune_api.py b/app/desktop/studio_server/test_finetune_api.py index 81e0b3dc0..4ffcff98b 100644 --- a/app/desktop/studio_server/test_finetune_api.py +++ b/app/desktop/studio_server/test_finetune_api.py @@ -366,9 +366,8 @@ def test_get_finetune_hyperparameters_invalid_provider(client, mock_finetune_reg response = client.get("/api/finetune/hyperparameters/invalid_provider") assert response.status_code == 400 - assert ( - response.json()["detail"] == "Fine tune provider 'invalid_provider' not found" - ) + expected = "Invalid provider 'invalid_provider'. Valid providers are: " + assert expected in response.json()["detail"] def test_dataset_split_type_enum(): diff --git a/checks.sh b/checks.sh index 73f4d1ef7..bc8362a3a 100755 --- a/checks.sh +++ b/checks.sh @@ -13,11 +13,14 @@ cd "$(dirname "$0")" headerStart="\n\033[4;34m=== " headerEnd=" ===\033[0m\n" -echo "${headerStart}Checking Python: Ruff, format, check${headerEnd}" +echo "${headerStart}Checking Python Linting and Formatting (ruff)${headerEnd}" # I is import sorting, F401 is unused imports uvx ruff check --select I,F401 uvx ruff format --check . +echo "${headerStart}Checking Python Types (ty)${headerEnd}" +uvx ty check + echo "${headerStart}Checking for Misspellings${headerEnd}" if command -v misspell >/dev/null 2>&1; then find . -type f | grep -v "/node_modules/" | grep -v "/\." | grep -v "/dist/" | grep -v "/desktop/build/" | xargs misspell -error @@ -43,8 +46,5 @@ else echo "Skipping Web UI: no files changed" fi -echo "${headerStart}Checking Types${headerEnd}" -pyright . - echo "${headerStart}Running Python Tests${headerEnd}" python3 -m pytest --benchmark-quiet -q . diff --git a/libs/core/kiln_ai/adapters/adapter_registry.py b/libs/core/kiln_ai/adapters/adapter_registry.py index 90d433b96..31a1d1cb9 100644 --- a/libs/core/kiln_ai/adapters/adapter_registry.py +++ b/libs/core/kiln_ai/adapters/adapter_registry.py @@ -1,5 +1,7 @@ from os import getenv +from typing_extensions import assert_never + from kiln_ai import datamodel from kiln_ai.adapters.ml_model_list import ModelProviderName from kiln_ai.adapters.model_adapters.base_adapter import AdapterConfig, BaseAdapter @@ -13,7 +15,6 @@ ) from kiln_ai.datamodel.task import RunConfigProperties from kiln_ai.utils.config import Config -from kiln_ai.utils.exhaustive_error import raise_exhaustive_enum_error def adapter_for_task( @@ -196,4 +197,4 @@ def adapter_for_task( "Custom openai compatible provider is not a supported core provider. It should map to an actual provider." ) case _: - raise_exhaustive_enum_error(core_provider_name) + assert_never(core_provider_name) diff --git a/libs/core/kiln_ai/adapters/chat/chat_formatter.py b/libs/core/kiln_ai/adapters/chat/chat_formatter.py index b95386fbb..86b2ee1c2 100644 --- a/libs/core/kiln_ai/adapters/chat/chat_formatter.py +++ b/libs/core/kiln_ai/adapters/chat/chat_formatter.py @@ -5,8 +5,9 @@ from dataclasses import dataclass from typing import Dict, List, Literal, Optional +from typing_extensions import assert_never + from kiln_ai.datamodel.datamodel_enums import ChatStrategy -from kiln_ai.utils.exhaustive_error import raise_exhaustive_enum_error COT_FINAL_ANSWER_PROMPT = "Considering the above, return a final result." @@ -215,7 +216,7 @@ def get_chat_formatter( case ChatStrategy.single_turn_r1_thinking: return SingleTurnR1ThinkingFormatter(system_message, user_input) case _: - raise_exhaustive_enum_error(strategy) + assert_never(strategy) def format_user_message(input: Dict | str) -> str: @@ -227,7 +228,7 @@ def format_user_message(input: Dict | str) -> str: Returns: str: The formatted user message. """ - if isinstance(input, dict): - return json.dumps(input, ensure_ascii=False) + if isinstance(input, str): + return input - return input + return json.dumps(input, ensure_ascii=False) diff --git a/libs/core/kiln_ai/adapters/eval/base_eval.py b/libs/core/kiln_ai/adapters/eval/base_eval.py index 2e27fb0be..6d4029e35 100644 --- a/libs/core/kiln_ai/adapters/eval/base_eval.py +++ b/libs/core/kiln_ai/adapters/eval/base_eval.py @@ -2,13 +2,14 @@ from abc import abstractmethod from typing import Dict +from typing_extensions import assert_never + from kiln_ai.adapters.adapter_registry import adapter_for_task from kiln_ai.adapters.ml_model_list import ModelProviderName from kiln_ai.adapters.model_adapters.base_adapter import AdapterConfig from kiln_ai.datamodel.eval import Eval, EvalConfig, EvalScores from kiln_ai.datamodel.json_schema import validate_schema_with_value_error from kiln_ai.datamodel.task import RunConfig, TaskOutputRatingType, TaskRun -from kiln_ai.utils.exhaustive_error import raise_exhaustive_enum_error class BaseEval: @@ -158,7 +159,7 @@ def build_score_schema(cls, eval: Eval, allow_float_scores: bool = False) -> str # Skip custom rating types in evals continue case _: - raise_exhaustive_enum_error(output_score.type) + assert_never(output_score.type) properties[output_score_json_key] = property diff --git a/libs/core/kiln_ai/adapters/eval/registry.py b/libs/core/kiln_ai/adapters/eval/registry.py index b4b6722e5..be8ae9b9d 100644 --- a/libs/core/kiln_ai/adapters/eval/registry.py +++ b/libs/core/kiln_ai/adapters/eval/registry.py @@ -1,7 +1,8 @@ +from typing_extensions import assert_never + from kiln_ai.adapters.eval.base_eval import BaseEval from kiln_ai.adapters.eval.g_eval import GEval from kiln_ai.datamodel.eval import EvalConfigType -from kiln_ai.utils.exhaustive_error import raise_exhaustive_enum_error def eval_adapter_from_type(eval_config_type: EvalConfigType) -> type[BaseEval]: @@ -13,4 +14,4 @@ def eval_adapter_from_type(eval_config_type: EvalConfigType) -> type[BaseEval]: return GEval case _: # type checking will catch missing cases - raise_exhaustive_enum_error(eval_config_type) + assert_never(eval_config_type) diff --git a/libs/core/kiln_ai/adapters/fine_tune/dataset_formatter.py b/libs/core/kiln_ai/adapters/fine_tune/dataset_formatter.py index 3c5ee27b6..05317dc74 100644 --- a/libs/core/kiln_ai/adapters/fine_tune/dataset_formatter.py +++ b/libs/core/kiln_ai/adapters/fine_tune/dataset_formatter.py @@ -5,10 +5,11 @@ from typing import Any, Dict, Protocol from uuid import uuid4 +from typing_extensions import assert_never + from kiln_ai.adapters.chat.chat_formatter import ChatMessage, get_chat_formatter from kiln_ai.datamodel import DatasetSplit, TaskRun from kiln_ai.datamodel.datamodel_enums import THINKING_DATA_STRATEGIES, ChatStrategy -from kiln_ai.utils.exhaustive_error import raise_exhaustive_enum_error class DatasetFormat(str, Enum): @@ -93,7 +94,7 @@ def build_training_chat( response_msg = serialize_r1_style_message(thinking, final_output) chat_formatter.next_turn(response_msg) case _: - raise_exhaustive_enum_error(data_strategy) + assert_never(data_strategy) return chat_formatter.messages diff --git a/libs/core/kiln_ai/adapters/model_adapters/base_adapter.py b/libs/core/kiln_ai/adapters/model_adapters/base_adapter.py index f43347b79..2da1dc317 100644 --- a/libs/core/kiln_ai/adapters/model_adapters/base_adapter.py +++ b/libs/core/kiln_ai/adapters/model_adapters/base_adapter.py @@ -256,13 +256,13 @@ def generate_run( usage: Usage | None = None, ) -> TaskRun: # Convert input and output to JSON strings if they are dictionaries - input_str = ( - json.dumps(input, ensure_ascii=False) if isinstance(input, dict) else input + input_str: str = ( + input if isinstance(input, str) else json.dumps(input, ensure_ascii=False) ) output_str = ( - json.dumps(run_output.output, ensure_ascii=False) - if isinstance(run_output.output, dict) - else run_output.output + run_output.output + if isinstance(run_output.output, str) + else json.dumps(run_output.output, ensure_ascii=False) ) # If no input source is provided, use the human data source diff --git a/libs/core/kiln_ai/adapters/model_adapters/litellm_adapter.py b/libs/core/kiln_ai/adapters/model_adapters/litellm_adapter.py index 6a45d5ae6..762b8d60f 100644 --- a/libs/core/kiln_ai/adapters/model_adapters/litellm_adapter.py +++ b/libs/core/kiln_ai/adapters/model_adapters/litellm_adapter.py @@ -1,9 +1,10 @@ import logging -from typing import Any, Dict +from typing import Any, Dict, cast import litellm from litellm.types.utils import ChoiceLogprobs, Choices, ModelResponse from litellm.types.utils import Usage as LiteLlmUsage +from typing_extensions import assert_never import kiln_ai.datamodel as datamodel from kiln_ai.adapters.ml_model_list import ( @@ -19,7 +20,6 @@ ) from kiln_ai.adapters.model_adapters.litellm_config import LiteLlmConfig from kiln_ai.datamodel.task import run_config_from_run_config_properties -from kiln_ai.utils.exhaustive_error import raise_exhaustive_enum_error logger = logging.getLogger(__name__) @@ -152,12 +152,14 @@ def adapter_name(self) -> str: return "kiln_openai_compatible_adapter" async def response_format_options(self) -> dict[str, Any]: + # shouldn't be needed - https://github.com/astral-sh/ty/issues/893 + typed_self = cast(LiteLlmAdapter, self) + # Unstructured if task isn't structured - if not self.has_structured_output(): + if not typed_self.has_structured_output(): return {} - structured_output_mode = self.run_config.structured_output_mode - + structured_output_mode = typed_self.run_config.structured_output_mode match structured_output_mode: case StructuredOutputMode.json_mode: return {"response_format": {"type": "json_object"}} @@ -190,7 +192,7 @@ async def response_format_options(self) -> dict[str, Any]: # See above, but this case should never happen. raise ValueError("Structured output mode is unknown.") case _: - raise_exhaustive_enum_error(structured_output_mode) + assert_never(structured_output_mode) def json_schema_response_format(self) -> dict[str, Any]: output_schema = self.task().output_schema() @@ -303,7 +305,9 @@ def litellm_model_id(self) -> str: litellm_provider_name: str | None = None is_custom = False - match provider.name: + # cast shouldn't be needed, but type inference is not working + provider_name = cast(ModelProviderName, provider.name) + match provider_name: case ModelProviderName.openrouter: litellm_provider_name = "openrouter" case ModelProviderName.openai: @@ -337,7 +341,7 @@ def litellm_model_id(self) -> str: case ModelProviderName.kiln_fine_tune: is_custom = True case _: - raise_exhaustive_enum_error(provider.name) + assert_never(provider.name) if is_custom: if self._api_base is None: diff --git a/libs/core/kiln_ai/adapters/model_adapters/test_litellm_adapter.py b/libs/core/kiln_ai/adapters/model_adapters/test_litellm_adapter.py index 4e016de9a..a401dfb2b 100644 --- a/libs/core/kiln_ai/adapters/model_adapters/test_litellm_adapter.py +++ b/libs/core/kiln_ai/adapters/model_adapters/test_litellm_adapter.py @@ -343,13 +343,8 @@ def test_litellm_model_id_unknown_provider(config, mock_task): mock_provider.model_id = "test-model" with patch.object(adapter, "model_provider", return_value=mock_provider): - with patch( - "kiln_ai.adapters.model_adapters.litellm_adapter.raise_exhaustive_enum_error" - ) as mock_raise_error: - mock_raise_error.side_effect = Exception("Test error") - - with pytest.raises(Exception, match="Test error"): - adapter.litellm_model_id() + with pytest.raises(AssertionError, match="Expected code to be unreachable"): + adapter.litellm_model_id() @pytest.mark.parametrize( diff --git a/libs/core/kiln_ai/adapters/parsers/parser_registry.py b/libs/core/kiln_ai/adapters/parsers/parser_registry.py index 30d539ce4..314ad4167 100644 --- a/libs/core/kiln_ai/adapters/parsers/parser_registry.py +++ b/libs/core/kiln_ai/adapters/parsers/parser_registry.py @@ -1,7 +1,8 @@ +from typing_extensions import assert_never + from kiln_ai.adapters.ml_model_list import ModelParserID from kiln_ai.adapters.parsers.base_parser import BaseParser from kiln_ai.adapters.parsers.r1_parser import R1ThinkingParser -from kiln_ai.utils.exhaustive_error import raise_exhaustive_enum_error def model_parser_from_id(parser_id: ModelParserID | None) -> BaseParser: @@ -16,4 +17,4 @@ def model_parser_from_id(parser_id: ModelParserID | None) -> BaseParser: case ModelParserID.optional_r1_thinking: return R1ThinkingParser(allow_missing_thinking=True) case _: - raise_exhaustive_enum_error(parser_id) + assert_never(parser_id) diff --git a/libs/core/kiln_ai/adapters/parsers/request_formatters.py b/libs/core/kiln_ai/adapters/parsers/request_formatters.py index c6a129249..a9d79cce2 100644 --- a/libs/core/kiln_ai/adapters/parsers/request_formatters.py +++ b/libs/core/kiln_ai/adapters/parsers/request_formatters.py @@ -1,8 +1,9 @@ import json from typing import Dict, Protocol +from typing_extensions import assert_never + from kiln_ai.adapters.ml_model_list import ModelFormatterID -from kiln_ai.utils.exhaustive_error import raise_exhaustive_enum_error class RequestFormatter(Protocol): @@ -37,4 +38,4 @@ def request_formatter_from_id( case ModelFormatterID.qwen3_style_no_think: return Qwen3StyleNoThinkFormatter() case _: - raise_exhaustive_enum_error(formatter_id) + assert_never(formatter_id) diff --git a/libs/core/kiln_ai/adapters/parsers/test_parser_registry.py b/libs/core/kiln_ai/adapters/parsers/test_parser_registry.py index 9ec9c38f7..7f00f8a9b 100644 --- a/libs/core/kiln_ai/adapters/parsers/test_parser_registry.py +++ b/libs/core/kiln_ai/adapters/parsers/test_parser_registry.py @@ -13,10 +13,10 @@ def test_model_parser_from_id_invalid(): class MockModelParserID: mock_value = "mock_value" - with pytest.raises(ValueError) as exc_info: + with pytest.raises(AssertionError) as exc_info: model_parser_from_id(MockModelParserID.mock_value) # type: ignore - assert "Unhandled enum value" in str(exc_info.value) + assert "Expected code to be unreachable" in str(exc_info.value) @pytest.mark.parametrize( diff --git a/libs/core/kiln_ai/adapters/parsers/test_request_formatters.py b/libs/core/kiln_ai/adapters/parsers/test_request_formatters.py index acc494dc3..c59487289 100644 --- a/libs/core/kiln_ai/adapters/parsers/test_request_formatters.py +++ b/libs/core/kiln_ai/adapters/parsers/test_request_formatters.py @@ -72,5 +72,5 @@ def test_request_formatter_factory(): def test_request_formatter_factory_invalid_id(): # Test with an invalid enum value by using a string that doesn't exist in the enum - with pytest.raises(ValueError, match="Unhandled enum value"): + with pytest.raises(AssertionError, match="Expected code to be unreachable"): request_formatter_from_id("invalid_formatter_id") # type: ignore diff --git a/libs/core/kiln_ai/adapters/prompt_builders.py b/libs/core/kiln_ai/adapters/prompt_builders.py index 398c3a4e0..e0546abeb 100644 --- a/libs/core/kiln_ai/adapters/prompt_builders.py +++ b/libs/core/kiln_ai/adapters/prompt_builders.py @@ -1,7 +1,8 @@ from abc import ABCMeta, abstractmethod +from typing_extensions import assert_never + from kiln_ai.datamodel import PromptGenerators, PromptId, Task, TaskRun -from kiln_ai.utils.exhaustive_error import raise_exhaustive_enum_error class BasePromptBuilder(metaclass=ABCMeta): @@ -425,4 +426,4 @@ def prompt_builder_from_id(prompt_id: PromptId, task: Task) -> BasePromptBuilder return MultiShotChainOfThoughtPromptBuilder(task) case _: # Type checking will find missing cases - raise_exhaustive_enum_error(typed_prompt_generator) + assert_never(typed_prompt_generator) diff --git a/libs/core/kiln_ai/adapters/provider_tools.py b/libs/core/kiln_ai/adapters/provider_tools.py index 3a0460ac1..2dc71331e 100644 --- a/libs/core/kiln_ai/adapters/provider_tools.py +++ b/libs/core/kiln_ai/adapters/provider_tools.py @@ -22,7 +22,6 @@ from kiln_ai.datamodel.registry import project_from_id from kiln_ai.datamodel.task import RunConfigProperties from kiln_ai.utils.config import Config -from kiln_ai.utils.exhaustive_error import raise_exhaustive_enum_error logger = logging.getLogger(__name__) @@ -385,8 +384,8 @@ def provider_name_from_id(id: str) -> str: case ModelProviderName.together_ai: return "Together AI" case _: - # triggers pyright warning if I miss a case - raise_exhaustive_enum_error(enum_id) + # triggers type error if I miss a case + assert_never(enum_id) return "Unknown provider: " + id diff --git a/libs/core/kiln_ai/datamodel/basemodel.py b/libs/core/kiln_ai/datamodel/basemodel.py index a8750b2ea..8db3ad528 100644 --- a/libs/core/kiln_ai/datamodel/basemodel.py +++ b/libs/core/kiln_ai/datamodel/basemodel.py @@ -416,7 +416,7 @@ class KilnParentModel(KilnBaseModel, metaclass=ABCMeta): def _create_child_method( cls, relationship_name: str, child_class: Type[KilnParentedModel] ): - def child_method(self, readonly: bool = False) -> list[child_class]: + def child_method(self, readonly: bool = False) -> list[child_class]: # type: ignore return child_class.all_children_of_parent_path(self.path, readonly=readonly) child_method.__name__ = relationship_name diff --git a/libs/core/kiln_ai/datamodel/eval.py b/libs/core/kiln_ai/datamodel/eval.py index 9f04213b6..1558d4fa3 100644 --- a/libs/core/kiln_ai/datamodel/eval.py +++ b/libs/core/kiln_ai/datamodel/eval.py @@ -1,9 +1,9 @@ import json from enum import Enum -from typing import TYPE_CHECKING, Any, Dict, List, Union +from typing import TYPE_CHECKING, Any, Dict, List, Union, cast from pydantic import BaseModel, Field, model_validator -from typing_extensions import Self +from typing_extensions import Self, assert_never from kiln_ai.datamodel.basemodel import ( ID_TYPE, @@ -15,7 +15,6 @@ from kiln_ai.datamodel.dataset_filters import DatasetFilterId from kiln_ai.datamodel.json_schema import string_to_json_key from kiln_ai.datamodel.task_run import Usage -from kiln_ai.utils.exhaustive_error import raise_exhaustive_enum_error if TYPE_CHECKING: from kiln_ai.datamodel.task import Task @@ -136,12 +135,16 @@ def validate_eval_run_types(self) -> Self: @model_validator(mode="after") def validate_scores(self) -> Self: + # shouldn't be needed - https://github.com/astral-sh/ty/issues/893 + typed_self = cast(EvalRun, self) # We're checking the scores have the expected keys from the grand-parent eval - if self.scores is None or len(self.scores) == 0: + if typed_self.scores is None or len(typed_self.scores) == 0: raise ValueError("scores are required, and must have at least one score.") - parent_eval_config = self.parent_eval_config() - eval = parent_eval_config.parent_eval() if parent_eval_config else None + parent_eval_config = typed_self.parent_eval_config() + if not parent_eval_config: + return self + eval = parent_eval_config.parent_eval() if not eval: # Can't validate without the grand-parent eval, allow it to be validated later return self @@ -190,8 +193,8 @@ def validate_scores(self) -> Self: f"Custom scores are not supported in evaluators. '{output_score.name}' was set to a custom score." ) case _: - # Catch missing cases - raise_exhaustive_enum_error(output_score.type) + # This should never happen since all enum cases are handled above + assert_never(output_score.type) return self diff --git a/libs/core/kiln_ai/datamodel/task_output.py b/libs/core/kiln_ai/datamodel/task_output.py index cee7f459c..6d6687d34 100644 --- a/libs/core/kiln_ai/datamodel/task_output.py +++ b/libs/core/kiln_ai/datamodel/task_output.py @@ -3,13 +3,12 @@ from typing import TYPE_CHECKING, Dict, List, Type, Union from pydantic import BaseModel, Field, ValidationInfo, model_validator -from typing_extensions import Self +from typing_extensions import Self, assert_never from kiln_ai.datamodel.basemodel import ID_TYPE, KilnBaseModel from kiln_ai.datamodel.datamodel_enums import TaskOutputRatingType from kiln_ai.datamodel.json_schema import validate_schema_with_value_error from kiln_ai.datamodel.strict_mode import strict_mode -from kiln_ai.utils.exhaustive_error import raise_exhaustive_enum_error if TYPE_CHECKING: from kiln_ai.datamodel.task import Task @@ -42,7 +41,7 @@ def normalize_rating(rating: float, rating_type: TaskOutputRatingType) -> float: case TaskOutputRatingType.custom: raise ValueError("Custom rating type can not be normalized") case _: - raise_exhaustive_enum_error(rating_type) + assert_never(rating_type) class TaskOutputRating(KilnBaseModel): diff --git a/libs/core/kiln_ai/utils/exhaustive_error.py b/libs/core/kiln_ai/utils/exhaustive_error.py deleted file mode 100644 index 6bfdf648b..000000000 --- a/libs/core/kiln_ai/utils/exhaustive_error.py +++ /dev/null @@ -1,6 +0,0 @@ -from typing import NoReturn - - -# Weird trick, but passing a enum to NoReturn triggers the type checker to complain unless all values are handled. -def raise_exhaustive_enum_error(value: NoReturn) -> NoReturn: - raise ValueError(f"Unhandled enum value: {value}") diff --git a/libs/core/pyproject.toml b/libs/core/pyproject.toml index 0d62a307b..7788c1e44 100644 --- a/libs/core/pyproject.toml +++ b/libs/core/pyproject.toml @@ -37,11 +37,11 @@ dependencies = [ [dependency-groups] dev = [ "isort>=5.13.2", - "pyright==1.1.376", "pytest-asyncio>=0.24.0", "pytest>=8.3.3", "python-dotenv>=1.0.1", "ruff>=0.9.0", + "ty>=0.0.1a11", ] [build-system] diff --git a/libs/core/uv.lock b/libs/core/uv.lock index 616c70852..c4964b7a0 100644 --- a/libs/core/uv.lock +++ b/libs/core/uv.lock @@ -771,7 +771,6 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "isort" }, - { name = "pyright" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "python-dotenv" }, @@ -796,7 +795,6 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "isort", specifier = ">=5.13.2" }, - { name = "pyright", specifier = ">=1.1.387" }, { name = "pytest", specifier = ">=8.3.3" }, { name = "pytest-asyncio", specifier = ">=0.24.0" }, { name = "python-dotenv", specifier = ">=1.0.1" }, @@ -1450,18 +1448,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, ] -[[package]] -name = "pyright" -version = "1.1.387" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nodeenv" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c2/32/e7187478d3105d6d7edc9b754d56472ee06557c25cc404911288fee1796a/pyright-1.1.387.tar.gz", hash = "sha256:577de60224f7fe36505d5b181231e3a395d427b7873be0bbcaa962a29ea93a60", size = 21939 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/18/c497df36641b0572f5bd59ae147b08ccaa6b8086397d50e1af97cc2ddcf6/pyright-1.1.387-py3-none-any.whl", hash = "sha256:6a1f495a261a72e12ad17e20d1ae3df4511223c773b19407cfa006229b1b08a5", size = 18577 }, -] + [[package]] name = "pytest" diff --git a/libs/server/pyproject.toml b/libs/server/pyproject.toml index bc4c76687..242b04dd9 100644 --- a/libs/server/pyproject.toml +++ b/libs/server/pyproject.toml @@ -30,11 +30,11 @@ dependencies = [ [dependency-groups] dev = [ "isort>=5.13.2", - "pyright==1.1.376", "pytest-asyncio>=0.24.0", "pytest>=8.3.3", "python-dotenv>=1.0.1", "ruff>=0.9.0", + "ty>=0.0.1a11", ] diff --git a/libs/server/uv.lock b/libs/server/uv.lock index 41d82c930..a8e84f681 100644 --- a/libs/server/uv.lock +++ b/libs/server/uv.lock @@ -799,7 +799,6 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "isort", specifier = ">=5.13.2" }, - { name = "pyright", specifier = ">=1.1.387" }, { name = "pytest", specifier = ">=8.3.3" }, { name = "pytest-asyncio", specifier = ">=0.24.0" }, { name = "python-dotenv", specifier = ">=1.0.1" }, @@ -821,7 +820,6 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "isort" }, - { name = "pyright" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "python-dotenv" }, @@ -840,7 +838,6 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "isort", specifier = ">=5.13.2" }, - { name = "pyright", specifier = ">=1.1.387" }, { name = "pytest", specifier = ">=8.3.3" }, { name = "pytest-asyncio", specifier = ">=0.24.0" }, { name = "python-dotenv", specifier = ">=1.0.1" }, @@ -1494,19 +1491,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, ] -[[package]] -name = "pyright" -version = "1.1.387" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nodeenv" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c2/32/e7187478d3105d6d7edc9b754d56472ee06557c25cc404911288fee1796a/pyright-1.1.387.tar.gz", hash = "sha256:577de60224f7fe36505d5b181231e3a395d427b7873be0bbcaa962a29ea93a60", size = 21939 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/18/c497df36641b0572f5bd59ae147b08ccaa6b8086397d50e1af97cc2ddcf6/pyright-1.1.387-py3-none-any.whl", hash = "sha256:6a1f495a261a72e12ad17e20d1ae3df4511223c773b19407cfa006229b1b08a5", size = 18577 }, -] - [[package]] name = "pytest" version = "8.3.3" diff --git a/pyproject.toml b/pyproject.toml index dc02656c7..ae18b5d3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,12 +15,12 @@ dependencies = [ [dependency-groups] dev = [ "isort>=5.13.2", - "pyright==1.1.376", "pytest-asyncio>=0.24.0", "pytest>=8.3.3", "pytest-xdist>=3.5", "python-dotenv>=1.0.1", "ruff>=0.9.0", + "ty>=0.0.1a16", ] @@ -39,14 +39,12 @@ kiln-server = { workspace = true } kiln-studio-desktop = { workspace = true } kiln-ai = { workspace = true } -[tool.pyright] -strictListInference = true -reportMissingTypeArgument = true - - [tool.ruff] exclude = [ ] +[tool.ty.src] +exclude = ["**/test_*.py", "app/desktop/build/**", "app/web_ui/**", "**/.venv"] + [tool.pytest.ini_options] addopts="-n auto" diff --git a/pyrightconfig.json b/pyrightconfig.json deleted file mode 100644 index ece7acd31..000000000 --- a/pyrightconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "exclude": ["**/test_*.py", "app/desktop/build/**", "app/web_ui/**", "**/.venv"], - "typeCheckingMode": "basic", - "autoImportCompletions": true, - "extraPaths": ["./"] -} diff --git a/uv.lock b/uv.lock index eab4b1c6c..531972076 100644 --- a/uv.lock +++ b/uv.lock @@ -989,11 +989,11 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "isort" }, - { name = "pyright" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "python-dotenv" }, { name = "ruff" }, + { name = "ty" }, ] [package.metadata] @@ -1017,11 +1017,11 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "isort", specifier = ">=5.13.2" }, - { name = "pyright", specifier = "==1.1.376" }, { name = "pytest", specifier = ">=8.3.3" }, { name = "pytest-asyncio", specifier = ">=0.24.0" }, { name = "python-dotenv", specifier = ">=1.0.1" }, { name = "ruff", specifier = ">=0.9.0" }, + { name = "ty", specifier = ">=0.0.1a11" }, ] [[package]] @@ -1038,12 +1038,12 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "isort" }, - { name = "pyright" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-xdist" }, { name = "python-dotenv" }, { name = "ruff" }, + { name = "ty" }, ] [package.metadata] @@ -1057,12 +1057,12 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "isort", specifier = ">=5.13.2" }, - { name = "pyright", specifier = "==1.1.376" }, { name = "pytest", specifier = ">=8.3.3" }, { name = "pytest-asyncio", specifier = ">=0.24.0" }, { name = "pytest-xdist", specifier = ">=3.5" }, { name = "python-dotenv", specifier = ">=1.0.1" }, { name = "ruff", specifier = ">=0.9.0" }, + { name = "ty", specifier = ">=0.0.1a16" }, ] [[package]] @@ -1082,11 +1082,11 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "isort" }, - { name = "pyright" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "python-dotenv" }, { name = "ruff" }, + { name = "ty" }, ] [package.metadata] @@ -1103,11 +1103,11 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "isort", specifier = ">=5.13.2" }, - { name = "pyright", specifier = "==1.1.376" }, { name = "pytest", specifier = ">=8.3.3" }, { name = "pytest-asyncio", specifier = ">=0.24.0" }, { name = "python-dotenv", specifier = ">=1.0.1" }, { name = "ruff", specifier = ">=0.9.0" }, + { name = "ty", specifier = ">=0.0.1a11" }, ] [[package]] @@ -1316,15 +1316,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/b7/b9e70fde2c0f0c9af4cc5277782a89b66d35948ea3369ec9f598358c3ac5/multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", size = 10051 }, ] -[[package]] -name = "nodeenv" -version = "1.9.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, -] - [[package]] name = "numpy" version = "2.2.3" @@ -1831,18 +1822,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/7a/78b512061af37a4466607143a9876192f04c5810b16e4cb097fbbfa02dc5/pyobjc_framework_Quartz-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:00a0933267e3a46ea4afcc35d117b2efb920f06de797fa66279c52e7057e3590", size = 226586 }, ] -[[package]] -name = "pyright" -version = "1.1.376" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nodeenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/dc/d0/c17d5b6ccdebe9f8becd75562d7c960727ec34a463ff363cb03f2624bc38/pyright-1.1.376.tar.gz", hash = "sha256:bffd63b197cd0810395bb3245c06b01f95a85ddf6bfa0e5644ed69c841e954dd", size = 17492 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/fd/35f6b518ff93bc09974ba88db5cacaf6ae3f231c10e25f7c069bb5c0af4e/pyright-1.1.376-py3-none-any.whl", hash = "sha256:0f2473b12c15c46b3207f0eec224c3cea2bdc07cd45dd4a037687cbbca0fbeff", size = 18222 }, -] - [[package]] name = "pystray" version = "0.19.5" @@ -2502,6 +2481,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/73/02342de9c2d20922115f787e101527b831c0cffd2105c946c4a4826bcfd4/tqdm-4.66.6-py3-none-any.whl", hash = "sha256:223e8b5359c2efc4b30555531f09e9f2f3589bcd7fdd389271191031b49b7a63", size = 78326 }, ] +[[package]] +name = "ty" +version = "0.0.1a16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/62/f021cdbdda9dbd553be4b841c2e9329ecd3ddc630a17c1ab5179832fbca8/ty-0.0.1a16.tar.gz", hash = "sha256:9ade26904870dc9bd988e58bad4382857f75ae05edb682ee0ba2f26fcc2d4c0f", size = 3961822 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/8d/fe6a4161ee493005d2d59bb02c37a746723eb65e45740cc8aa2367da5ddb/ty-0.0.1a16-py3-none-linux_armv6l.whl", hash = "sha256:dfb55d28df78ca40f8aff91ec3ae01f4b7bc23aa04c72ace7ec00fbc5e0468c0", size = 7840414 }, + { url = "https://files.pythonhosted.org/packages/88/85/70bef8b680216276e941480a0bac3d00b89d1d64d4e281bd3daaa85fc5ed/ty-0.0.1a16-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5a0e9917efadf2ec173ee755db3653243b64fa8b26fa4d740dea68e969a99898", size = 7979261 }, + { url = "https://files.pythonhosted.org/packages/5a/07/400b56734c7b8a29ea1d6927f36dd75bf263c8a223ea4bd05e25bdbbc8a2/ty-0.0.1a16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9253cb8b5c4052337b1600f581ecd8e6929e635a07ec9e8dc5cc2fa4008e6b3b", size = 7567959 }, + { url = "https://files.pythonhosted.org/packages/02/c9/095cb09e33a4d547a71f4f698d09f3f9edc92746e029945fe8412f59d421/ty-0.0.1a16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:374c059e184f8abc969e07965355ddbbf7205a713721d3867ee42f976249c9ac", size = 7697398 }, + { url = "https://files.pythonhosted.org/packages/48/39/e2ce5b1151dfc80659486f74113972bc994c39b8f7f39084b271d03c4b04/ty-0.0.1a16-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c5364c6d1a1a3d5b8e765a730303f8e07094ab9e63682aa82f73755d92749852", size = 7681504 }, + { url = "https://files.pythonhosted.org/packages/a3/44/5c1158bd3e2e939e5b0ddb6b15c8e158870fa44915b5535909f83d4bd4ed/ty-0.0.1a16-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f201ff0ab3267123b9e42cc8584a193aa76e6e0865003d1b0a41bd025f08229e", size = 8551057 }, + { url = "https://files.pythonhosted.org/packages/0d/20/2564cd89f1c06ce329ab25d91ce457d2dc00d2559c519111874811250442/ty-0.0.1a16-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:57f14207d88043ba27f4b84d84dfdaa1bfbcc5170d5f50814d2997cbc3d75366", size = 8999239 }, + { url = "https://files.pythonhosted.org/packages/41/5f/64b74a8aaa080267c71a9d591b980a9c915b2439034b9075520c56ef1e4b/ty-0.0.1a16-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:950c45e1d6c58e61ad77ed5d2d04f091e44b0d13e6d5d79143bb81078ab526b1", size = 8638649 }, + { url = "https://files.pythonhosted.org/packages/67/c7/80ad1c11d896cd1a52f24f0b3660ed368187ba152337b8f18b2d0591bd02/ty-0.0.1a16-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad133d0eac5291d738e40052df98ca9f194e0f0433d6086a4890fd6733217969", size = 8443175 }, + { url = "https://files.pythonhosted.org/packages/94/6c/eb3c214a44bd0f6ad359c1ce28de62cbaecfd5823553a35b0163e9f3e738/ty-0.0.1a16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e59f877ef8b967c06173a7a663271a6e66edb049f0db00f7873be5e41d61d5b", size = 8278214 }, + { url = "https://files.pythonhosted.org/packages/18/ab/f44474a526f3a1ac770c8839a23fac51f93a4ad5e6ec2770d74e20bd5684/ty-0.0.1a16-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4e973b8cb2c382263aaf77a40889ad236bd06ddca671cc973f9e33e8e02f0af1", size = 7591502 }, + { url = "https://files.pythonhosted.org/packages/b9/d3/825975f1277b097883ed3428c23e0e8f67ed4fffd25d00b8b60650b663cb/ty-0.0.1a16-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4a82d9c4b76a73aff60cab93b71f2dd83952c2eb68a86578e1db56aee8f7e338", size = 7715602 }, + { url = "https://files.pythonhosted.org/packages/f8/f0/2805b4172c46b832c2efa368731d4aa4af0aa35ce120a4726ccdb3b102a0/ty-0.0.1a16-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7993f48def35f1707a2dc675bf7d08906cc5f26204b0b479746664301eda15b9", size = 8156780 }, + { url = "https://files.pythonhosted.org/packages/25/a5/f47c11a3dc52b3e148aaaa2bf7c37ea75998cfd50ad5f4b56fd2cc79c708/ty-0.0.1a16-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d9887ec65984e7dbf3b5e906ef44e8f47ff5351c7ac04d49e793b324d744050f", size = 8350253 }, + { url = "https://files.pythonhosted.org/packages/1e/e4/498c0bed38385d0c8babfe5707fe157700ae698d77dd9a1a8ffaaa97baea/ty-0.0.1a16-py3-none-win32.whl", hash = "sha256:4113a176a8343196d73145668460873d26ccef8766ff4e5287eec2622ce8754d", size = 7460041 }, + { url = "https://files.pythonhosted.org/packages/af/9e/5a8a690a5542405fd20cab6b0aa97a5af76de1e39582de545fac48e53f3a/ty-0.0.1a16-py3-none-win_amd64.whl", hash = "sha256:508ba4c50bc88f1a7c730d40f28d6c679696ee824bc09630c7c6763911de862a", size = 8074666 }, + { url = "https://files.pythonhosted.org/packages/dc/53/2a2eb8cc22b3e12d2040ed78d98842d0dddfa593d824b7ff60e30afe6f41/ty-0.0.1a16-py3-none-win_arm64.whl", hash = "sha256:36f53e430b5e0231d6b6672160c981eaf7f9390162380bcd1096941b2c746b5d", size = 7612948 }, +] + [[package]] name = "typer" version = "0.15.2"