diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 7c359c456..2646d3255 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,7 @@ { "recommendations": [ "ms-python.python", - "ms-python.vscode-pylance", + "astral-sh.ty", "charliermarsh.ruff", "esbenp.prettier-vscode", "EditorConfig.EditorConfig", diff --git a/.vscode/settings.json b/.vscode/settings.json index a70e6cc86..ae82e92ca 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "python.languageServer": "Pylance", + "python.languageServer": "None", "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python", "python.analysis.inlayHints.functionReturnTypes": true, "python.analysis.inlayHints.variableTypes": true, diff --git a/justfile b/justfile index d48bc503a..64ed8a2e9 100644 --- a/justfile +++ b/justfile @@ -18,7 +18,7 @@ fmt: uv run ruff format . lint: - uv run pyright src/yutto packages/biliass/src/biliass tests + uv run ty check --error-on-warning src/yutto packages/biliass/src/biliass tests uv run ruff check . uv run typos diff --git a/packages/biliass/pyproject.toml b/packages/biliass/pyproject.toml index ed7663b20..d6c4ee6bb 100644 --- a/packages/biliass/pyproject.toml +++ b/packages/biliass/pyproject.toml @@ -39,11 +39,6 @@ biliass = "biliass.__main__:main" features = ["pyo3/extension-module"] module-name = "biliass._core" -[tool.pyright] -include = ["src/biliass"] -pythonVersion = "3.10" -typeCheckingMode = "basic" - [build-system] requires = ["maturin>=1.4,<2.0"] build-backend = "maturin" diff --git a/pyproject.toml b/pyproject.toml index 69b3762cb..bc05814d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,14 +45,13 @@ yutto = "yutto.__main__:main" [dependency-groups] dev = [ - "pyright>=1.1.408", + "ty>=0.0.31", "ruff>=0.15.0", "typos>=1.43.3", "pytest>=9.0.2", "pytest-rerunfailures>=16.1", "syrupy>=5.1.0", "pytest-codspeed>=4.2.0", - "tomli>=2.3.0; python_version >= '3.11'", ] [tool.uv.sources] @@ -64,11 +63,6 @@ members = ["packages/*"] [tool.pytest.ini_options] markers = ["api", "e2e", "processor", "biliass", "ignore", "ci_skip", "ci_only"] -[tool.pyright] -include = ["src/yutto", "packages/biliass/src/biliass", "tests"] -pythonVersion = "3.10" -typeCheckingMode = "strict" - [tool.ruff] line-length = 120 target-version = "py310" @@ -113,8 +107,6 @@ select = [ ] ignore = [ "E501", # line too long, duplicate with ruff fmt - "F401", # imported but unused, duplicate with pyright - "F841", # local variable is assigned to but never used, duplicate with pyright ] future-annotations = true diff --git a/src/yutto/api/ugc_video.py b/src/yutto/api/ugc_video.py index 278dd52d7..58f31be9e 100644 --- a/src/yutto/api/ugc_video.py +++ b/src/yutto/api/ugc_video.py @@ -358,7 +358,7 @@ def _parse_actor_info(video_info: dict[str, Any]): actors: list[Actor] = [] if video_info.get("staff") and isinstance(video_info["staff"], list): _index: int = 0 - staff_list: list[dict[str, Any]] = video_info["staff"] # pyright: ignore[reportUnknownVariableType] + staff_list: list[dict[str, Any]] = video_info["staff"] for staff in staff_list: actors.append( Actor( @@ -371,7 +371,7 @@ def _parse_actor_info(video_info: dict[str, Any]): ) _index += 1 elif video_info.get("owner") and isinstance(video_info["owner"], dict): - staff_info: dict[str, Any] = video_info["owner"] # pyright: ignore[reportUnknownVariableType] + staff_info: dict[str, Any] = video_info["owner"] actors.append( Actor( name=staff_info["name"], diff --git a/src/yutto/api/user_info.py b/src/yutto/api/user_info.py index 8538a0935..8466aeadd 100644 --- a/src/yutto/api/user_info.py +++ b/src/yutto/api/user_info.py @@ -98,7 +98,7 @@ def _get_mixin_key(string: str) -> str: return "".join([string[idx] for idx in char_indices[:32]]) -def encode_wbi(params: dict[str, Any], wbi_img: WbiImg): +def encode_wbi(params: dict[str, Any], wbi_img: WbiImg) -> dict[str, Any]: img_key = wbi_img["img_key"] sub_key = wbi_img["sub_key"] illegal_char_remover = re.compile(r"[!'\(\)*]") diff --git a/src/yutto/cli/cli.py b/src/yutto/cli/cli.py index 3cfa08bd5..15e79239f 100644 --- a/src/yutto/cli/cli.py +++ b/src/yutto/cli/cli.py @@ -56,7 +56,7 @@ def parse_config_path() -> Path | None: def cli() -> argparse.ArgumentParser: settings_file = parse_config_path() if settings_file is None: - settings = YuttoSettings() # pyright: ignore[reportCallIssue] + settings = YuttoSettings() # ty: ignore[missing-argument] else: Logger.info(f"发现配置文件 {settings_file},加载中……") settings = load_settings_file(settings_file) diff --git a/src/yutto/cli/settings.py b/src/yutto/cli/settings.py index 018df336b..e993400e6 100644 --- a/src/yutto/cli/settings.py +++ b/src/yutto/cli/settings.py @@ -101,11 +101,11 @@ class YuttoAuthSettings(BaseModel): class YuttoSettings(BaseModel): - basic: Annotated[YuttoBasicSettings, Field(YuttoBasicSettings())] # pyright: ignore[reportCallIssue] - resource: Annotated[YuttoResourceSettings, Field(YuttoResourceSettings())] # pyright: ignore[reportCallIssue] - danmaku: Annotated[YuttoDanmakuSettings, Field(YuttoDanmakuSettings())] # pyright: ignore[reportCallIssue] - batch: Annotated[YuttoBatchSettings, Field(YuttoBatchSettings())] # pyright: ignore[reportCallIssue] - auth: Annotated[YuttoAuthSettings, Field(YuttoAuthSettings())] # pyright: ignore[reportCallIssue] + basic: Annotated[YuttoBasicSettings, Field(YuttoBasicSettings())] # ty: ignore[missing-argument] + resource: Annotated[YuttoResourceSettings, Field(YuttoResourceSettings())] # ty: ignore[missing-argument] + danmaku: Annotated[YuttoDanmakuSettings, Field(YuttoDanmakuSettings())] # ty: ignore[missing-argument] + batch: Annotated[YuttoBatchSettings, Field(YuttoBatchSettings())] # ty: ignore[missing-argument] + auth: Annotated[YuttoAuthSettings, Field(YuttoAuthSettings())] # ty: ignore[missing-argument] def search_for_settings_file() -> Path | None: @@ -123,7 +123,7 @@ def search_for_settings_file() -> Path | None: def load_settings_file(settings_file: Path) -> YuttoSettings: with settings_file.open("r", encoding="utf-8") as f: - settings_raw: Any = tomllib.loads(f.read()) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType] + settings_raw: Any = tomllib.loads(f.read()) return YuttoSettings.model_validate(settings_raw) diff --git a/src/yutto/input_parser.py b/src/yutto/input_parser.py index 3330a1a8c..dbb6d52ae 100644 --- a/src/yutto/input_parser.py +++ b/src/yutto/input_parser.py @@ -36,8 +36,8 @@ def alias_parser(file_path: str) -> dict[str, str]: def file_scheme_parser(url: str) -> list[str]: - file_url: str = urllib.parse.urlparse(url).path # pyright: ignore[reportUnknownMemberType,reportAttributeAccessIssue,reportUnknownVariableType] - file_path = path_from_cli(urllib.request.url2pathname(file_url)) # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType] + file_url: str = urllib.parse.urlparse(url).path + file_path = path_from_cli(urllib.request.url2pathname(file_url)) Logger.info(f"解析下载列表 {file_path} 中...") result: list[str] = [] with file_path.open("r", encoding="utf-8") as f: diff --git a/src/yutto/utils/asynclib.py b/src/yutto/utils/asynclib.py index c75cf2daf..0c2988690 100644 --- a/src/yutto/utils/asynclib.py +++ b/src/yutto/utils/asynclib.py @@ -23,7 +23,7 @@ def initial_async_policy(): if sys.version_info < (3, 11) and platform.system() == "Windows": Logger.debug("Windows 平台(Python < 3.11),单独设置 EventLoopPolicy") - asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) # pyright: ignore + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) # ty: ignore[unresolved-attribute] class CoroutineWrapper(Generic[RetT]): @@ -50,9 +50,9 @@ async def sleep_with_status_bar_refresh(seconds: float): def async_cache( args_to_cache_key: Callable[[inspect.BoundArguments], str], ) -> Callable[[Callable[P, Coroutine[Any, Any, RetT]]], Callable[P, Coroutine[Any, Any, RetT]]]: - CACHE: dict[str, RetT] = {} - def decorator(fn: Callable[P, Coroutine[Any, Any, RetT]]) -> Callable[P, Coroutine[Any, Any, RetT]]: + CACHE: dict[str, RetT] = {} + @wraps(fn) async def wrapper(*args: P.args, **kwargs: P.kwargs) -> RetT: assert isinstance(fn, types.FunctionType) diff --git a/src/yutto/utils/fetcher.py b/src/yutto/utils/fetcher.py index 6ced333c5..7ad75eecc 100644 --- a/src/yutto/utils/fetcher.py +++ b/src/yutto/utils/fetcher.py @@ -229,7 +229,7 @@ async def get_size(ctx: FetcherContext, client: AsyncClient, url: str) -> int | @MaxRetry(2) # 对于相同 session,同样的页面没必要重复 touch @async_cache(lambda args: f"client_id={id(args.arguments['client'])}, url={args.arguments['url']}") - async def touch_url(ctx: FetcherContext, client: AsyncClient, url: str): + async def touch_url(ctx: FetcherContext, client: AsyncClient, url: str) -> None: async with ctx.fetch_guard(): Logger.debug(f"Touch url: {url}") await client.get(url) diff --git a/src/yutto/utils/file_buffer.py b/src/yutto/utils/file_buffer.py index de20b2a1f..f87bc2261 100644 --- a/src/yutto/utils/file_buffer.py +++ b/src/yutto/utils/file_buffer.py @@ -3,7 +3,7 @@ import heapq from dataclasses import dataclass, field from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast import aiofiles @@ -13,7 +13,12 @@ if TYPE_CHECKING: from types import TracebackType - from typing_extensions import Self + from typing_extensions import Protocol, Self + + class AsyncWritableBinaryFile(Protocol): + async def write(self, data: bytes) -> int: ... + + async def close(self) -> None: ... @dataclass(order=True) @@ -53,7 +58,9 @@ async def __ainit__(self, file_path: str | Path, overwrite: bool = False): self.file_path.unlink(missing_ok=True) self.buffer = list[BufferChunk]() self.written_size = self.file_path.stat().st_size if not overwrite and self.file_path.exists() else 0 - self.file_obj: aiofiles.threadpool.binary.AsyncBufferedIOBase | None = await aiofiles.open(file_path, "ab") + self.file_obj: AsyncWritableBinaryFile | None = cast( + "AsyncWritableBinaryFile", await aiofiles.open(file_path, "ab") + ) async def write(self, chunk: bytes, offset: int): buffer_chunk = BufferChunk(offset, chunk) diff --git a/src/yutto/utils/functional/data_access.py b/src/yutto/utils/functional/data_access.py index 17407f493..a8438255f 100644 --- a/src/yutto/utils/functional/data_access.py +++ b/src/yutto/utils/functional/data_access.py @@ -14,4 +14,4 @@ def data_has_chained_keys(data: Any, keys: list[str]) -> bool: if not isinstance(data, dict): return False key, *remaining_keys = keys - return data_has_chained_keys(data.get(key, Undefined()), remaining_keys) # pyright: ignore[reportUnknownMemberType] + return data_has_chained_keys(data.get(key, Undefined()), remaining_keys) diff --git a/tests/test_api/test_bangumi.py b/tests/test_api/test_bangumi.py index a1c31a233..91c1cc96b 100644 --- a/tests/test_api/test_bangumi.py +++ b/tests/test_api/test_bangumi.py @@ -5,7 +5,7 @@ from yutto.api.bangumi import ( get_bangumi_list, get_bangumi_playurl, - get_bangumi_subtitles, # pyright: ignore[reportUnusedImport] + get_bangumi_subtitles, # noqa: F401 get_season_id_by_episode_id, get_season_id_by_media_id, ) diff --git a/tests/test_utils/test_fetcher.py b/tests/test_utils/test_fetcher.py index 39d17b9eb..d90b319cf 100644 --- a/tests/test_utils/test_fetcher.py +++ b/tests/test_utils/test_fetcher.py @@ -2,13 +2,27 @@ import asyncio import ssl -from typing import Any +from typing import Any, Protocol, cast import pytest from yutto.utils.fetcher import FetcherContext, create_client, create_sync_client, resolve_proxy +class _HasSSLContext(Protocol): + _ssl_context: ssl.SSLContext + + +class _HasPool(Protocol): + _pool: _HasSSLContext + + +def _transport_ssl_context(transport: Any) -> ssl.SSLContext: + # Test helper: inspect httpx's private transport internals to assert TLS policy wiring. + # If httpx changes `_pool._ssl_context`, this assertion helper will need to be updated too. + return cast("_HasPool", transport)._pool._ssl_context + + def test_resolve_proxy_auto_uses_system_proxy(): assert resolve_proxy("auto") == (None, True) @@ -34,8 +48,8 @@ def test_resolve_proxy_rejects_invalid_scheme(): def test_create_client_keeps_download_tls_verification_disabled(): client = create_client() try: - transport: Any = client._transport # pyright: ignore[reportPrivateUsage] - ssl_context: ssl.SSLContext = transport._pool._ssl_context + transport: Any = client._transport + ssl_context = _transport_ssl_context(transport) assert ssl_context.verify_mode == ssl.CERT_NONE assert not ssl_context.check_hostname finally: @@ -45,8 +59,8 @@ def test_create_client_keeps_download_tls_verification_disabled(): def test_create_sync_client_follows_default_download_tls_policy(): client = create_sync_client() try: - transport: Any = client._transport # pyright: ignore[reportPrivateUsage] - ssl_context: ssl.SSLContext = transport._pool._ssl_context + transport: Any = client._transport + ssl_context = _transport_ssl_context(transport) assert ssl_context.verify_mode == ssl.CERT_NONE assert not ssl_context.check_hostname finally: @@ -56,8 +70,8 @@ def test_create_sync_client_follows_default_download_tls_policy(): def test_create_sync_client_can_enable_tls_verification(): client = create_sync_client(verify=True) try: - transport: Any = client._transport # pyright: ignore[reportPrivateUsage] - ssl_context: ssl.SSLContext = transport._pool._ssl_context + transport: Any = client._transport + ssl_context = _transport_ssl_context(transport) assert ssl_context.verify_mode == ssl.CERT_REQUIRED assert ssl_context.check_hostname finally: diff --git a/uv.lock b/uv.lock index f6b019553..95048a343 100644 --- a/uv.lock +++ b/uv.lock @@ -285,15 +285,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] -[[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, upload-time = "2024-06-04T18:44:11.171Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, -] - [[package]] name = "packaging" version = "25.0" @@ -463,19 +454,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] -[[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" @@ -671,6 +649,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, ] +[[package]] +name = "ty" +version = "0.0.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/cc/5ea5d3a72216c8c2bf77d83066dd4f3553532d0aacc03d4a8397dd9845e1/ty-0.0.31.tar.gz", hash = "sha256:4a4094292d9671caf3b510c7edf36991acd9c962bb5d97205374ffed9f541c45", size = 5516619, upload-time = "2026-04-15T15:47:59.87Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/10/ea805cbbd75d5d50792551a2b383de8521eeab0c44f38c73e12819ced65e/ty-0.0.31-py3-none-linux_armv6l.whl", hash = "sha256:761651dc17ad7bc0abfc1b04b3f0e84df263ed435d34f29760b3da739ab02d35", size = 10834749, upload-time = "2026-04-15T15:48:14.877Z" }, + { url = "https://files.pythonhosted.org/packages/d9/4c/fabf951850401d24d36b21bced088a366c6827e1c37dab4523afff84c4b2/ty-0.0.31-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c529922395a07231c27488f0290651e05d27d149f7e0aa807678f1f7e9c58a5e", size = 10626012, upload-time = "2026-04-15T15:48:22.554Z" }, + { url = "https://files.pythonhosted.org/packages/04/b0/4a5aff88d2544f19514a59c8f693d63144aa7307fe2ee5df608333ab5460/ty-0.0.31-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5f345df2b87d747859e72c2cbc9be607ea1bbc8bc93dd32fa3d03ea091cb4fee", size = 10075790, upload-time = "2026-04-15T15:47:46.959Z" }, + { url = "https://files.pythonhosted.org/packages/d5/73/9d4dcad12cd4e85274014f2c0510ef93f590b2a1e5148de3a9f276098dad/ty-0.0.31-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4b207eddcfbafd376132689d3435b14efcb531289cb59cd961c6a611133bd54", size = 10590286, upload-time = "2026-04-15T15:48:06.222Z" }, + { url = "https://files.pythonhosted.org/packages/47/45/fe40adde18692359ded174ae7ddbfac056e876eb0f43b65be74fde7f6072/ty-0.0.31-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:663778b220f357067488ce68bfc52335ccbd161549776f70dcbde6bbde82f77a", size = 10623824, upload-time = "2026-04-15T15:48:12.965Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e8/0ffa2e09b548e6daa9ebc368d68b767dc2405ca4cbeadb7ede0e2cb21059/ty-0.0.31-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3506cfe87dfade0fb2960dd4fffd4fd8089003587b3445c0a1a295c9d83764fb", size = 11156864, upload-time = "2026-04-15T15:48:08.473Z" }, + { url = "https://files.pythonhosted.org/packages/08/e9/fd44c2075115d569593ee9473d7e2a38b750fd7e783421c95eb528c15df5/ty-0.0.31-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b3f3d8492f08e81916026354c1d1599e9ddfa1241804141a74d5662fc710085", size = 11696401, upload-time = "2026-04-15T15:48:17.355Z" }, + { url = "https://files.pythonhosted.org/packages/4e/50/35aad8eadf964d23e2a4faa5b38a206aa85c78833c8ce335dddd2c34ba63/ty-0.0.31-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a97de32ee6a619393a4c495e056a1c547de7877510f3152e61345c71d774d2d0", size = 11374903, upload-time = "2026-04-15T15:47:55.893Z" }, + { url = "https://files.pythonhosted.org/packages/c8/37/01eccd25d23f5aaa7f7ff1a87b5b215469f6b202cf689a1812b71c1e7f6b/ty-0.0.31-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c906354ce441e342646582bc9b8f48a676f79f3d061e25de15ff870e015ca14e", size = 11206624, upload-time = "2026-04-15T15:47:51.778Z" }, + { url = "https://files.pythonhosted.org/packages/f4/70/baad2914cb097453f127a221f8addb2b41926098059cd773c75e6a662fc4/ty-0.0.31-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:275bb7c82afcbf89fe2dbef1b2692f2bc98451f1ee2c8eb809ddd91317822388", size = 10575089, upload-time = "2026-04-15T15:47:49.448Z" }, + { url = "https://files.pythonhosted.org/packages/83/12/bae3a7bba2e785eb72ce00f9da70eedcb8c5e8299efecbd16e6e436abd82/ty-0.0.31-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:405da247027c6efd1e264886b6ac4a86ab3a4f09200b02e33630efe85f119e53", size = 10642315, upload-time = "2026-04-15T15:48:19.661Z" }, + { url = "https://files.pythonhosted.org/packages/93/9e/cad04d5d839bc60355cea98c7e09d724ea65f47184def0fae8b90dc54591/ty-0.0.31-py3-none-musllinux_1_2_i686.whl", hash = "sha256:54d9835608eed196853d6643f645c50ce83bcc7fe546cdb3e210c1bcf7c58c09", size = 10834473, upload-time = "2026-04-15T15:48:02.091Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ba/84112d280182d37690d3d2b4018b2667e42bc281585e607015635310016a/ty-0.0.31-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5ee11be9b07e8c0c6b455ff075a0abe4f194de9476f57624db98eec9df618355", size = 11315785, upload-time = "2026-04-15T15:48:10.754Z" }, + { url = "https://files.pythonhosted.org/packages/50/9f/ac42dc223d7e0950e97a1854567a8b3e7fe09ad7375adbf91bfb43290482/ty-0.0.31-py3-none-win32.whl", hash = "sha256:7286587aacf3eef0956062d6492b893b02f82b0f22c5e230008e13ff0d216a8b", size = 10187657, upload-time = "2026-04-15T15:48:04.264Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/57ba7ea7ecb2f4751644ba91756e2be70e33ef5952c0c41a256a0e4c2437/ty-0.0.31-py3-none-win_amd64.whl", hash = "sha256:81134e25d2a2562ab372f24de8f9bd05034d27d30377a5d7540f259791c6234c", size = 11205258, upload-time = "2026-04-15T15:47:53.759Z" }, + { url = "https://files.pythonhosted.org/packages/88/39/bca669095ccf0a400af941fdf741578d4c2d6719f1b7f10e6dbec10aa862/ty-0.0.31-py3-none-win_arm64.whl", hash = "sha256:e9cb15fad26545c6a608f40f227af3a5513cb376998ca6feddd47ca7d93ffafa", size = 10590392, upload-time = "2026-04-15T15:47:57.968Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" @@ -728,13 +730,12 @@ dependencies = [ [package.dev-dependencies] dev = [ - { name = "pyright" }, { name = "pytest" }, { name = "pytest-codspeed" }, { name = "pytest-rerunfailures" }, { name = "ruff" }, { name = "syrupy" }, - { name = "tomli", marker = "python_full_version >= '3.11'" }, + { name = "ty" }, { name = "typos" }, ] @@ -754,12 +755,11 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ - { name = "pyright", specifier = ">=1.1.408" }, { name = "pytest", specifier = ">=9.0.2" }, { name = "pytest-codspeed", specifier = ">=4.2.0" }, { name = "pytest-rerunfailures", specifier = ">=16.1" }, { name = "ruff", specifier = ">=0.15.0" }, { name = "syrupy", specifier = ">=5.1.0" }, - { name = "tomli", marker = "python_full_version >= '3.11'", specifier = ">=2.3.0" }, + { name = "ty", specifier = ">=0.0.31" }, { name = "typos", specifier = ">=1.43.3" }, ]