[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "torrcast" dynamic = ["version"] description = "Поиск торрент-релиза и каст его на ТВ без скачивания и без облака" readme = "README.md" # Debian 12 везёт python3.11 и никакого python3.12 в репозиториях у неё нет. Нижней # границе это больше не указ: где системного 3.12 нет, установщик приносит его сам # через uv (install.sh, pick_python), и установка остаётся теми же двумя командами. requires-python = ">=3.12" license = { text = "MIT" } authors = [{ name = "anysda", email = "anysdaw@gmail.com" }] dependencies = [ "requests>=2.32", "PySocks>=1.7", # Не catt: он гонит любой URL через yt-dlp и не умеет подсказки формата HLS, # без которых Default Media Receiver отвечает LOAD ERROR. "PyChromecast>=14", # Мост Home Assistant объявляет себя по mDNS (hass/announce.py). Библиотека и так # приезжает вместе с PyChromecast - он ищет приёмники ею же, - но зависимость # названа прямо: транзитивная держится чужим решением, а мост зовёт её сам. "zeroconf>=0.130", ] [project.scripts] cast = "torrcast.runtime.main:main" torrcast-bot = "tgbot.bot:_bot" torrcast-ha = "hass.main:main" [dependency-groups] dev = [ "grimp>=3.15", "mypy>=1.11", "pyte>=0.8", "pytest>=8.3", "pytest-deadfixtures>=3.1", "pytest-testmon>=2.1", "pytest-xdist>=3.8", "ruff==0.16.1", "types-requests>=2.32", "vulture>=2.16", ] # Единственное ручное место с номером версии - torrcast/domain/version.py. Отсюда # hatchling берёт версию собранного пакета сам; остальные производные места (JSON- # манифест интеграции, install.sh, uv.lock) читать python на лету не умеют и разносятся # командой scripts/set-version.py (сторож - tests/test_version_places.py). [tool.hatch.version] path = "torrcast/domain/version.py" [tool.hatch.build.targets.wheel] packages = ["torrcast", "tgbot", "hass"] [tool.ruff] line-length = 100 target-version = "py312" [tool.ruff.lint] # ARG - параметр, который никто не читает: это мёртвый код в малом, и он же первый # признак договора, который разъехался с телом. ERA - закомментированный код: по дереву # он даёт ноль, и правило стоит здесь ровно затем, чтобы так и осталось. select = ["E", "F", "W", "I", "N", "UP", "B", "C4", "SIM", "RUF", "ARG", "ERA"] # RUF001-003: кириллица в строках/докстрингах/комментариях — не «неоднозначные символы». ignore = ["RUF001", "RUF002", "RUF003"] [tool.ruff.lint.per-file-ignores] # Подделка порта обязана держать сигнатуру договора целиком, даже когда телу подделки # половина параметров не нужна: сценарий зовёт её по договору, а не по телу. Убрать # «лишний» параметр здесь значит сломать подделку молча. То же у обвязки в scripts: # колбэки чужих библиотек принимают то, что им передают. "tests/**" = ["ARG"] "scripts/**" = ["ARG"] # Белый список vulture - не код, а перечень имён: они здесь намеренно не определены # (F821) и стоят голыми выражениями (B018), потому что читает файл только vulture. "scripts/vulture-whitelist.py" = ["F821", "B018"] [tool.ruff.lint.isort] known-first-party = ["torrcast", "tgbot", "hass", "custom_components"] [tool.mypy] python_version = "3.12" strict = true incremental = false files = ["torrcast", "tgbot", "hass", "custom_components", "tests", "scripts"] # Белый список vulture переехал в `scripts`, то есть в охват mypy, но кодом он не # является: имена в нём намеренно нигде не определены, их читает только vulture. exclude = ['scripts/vulture-whitelist\.py$'] warn_unreachable = true # Без этих двух строк `custom_components/torrcast/` читается как модуль `torrcast` и # сталкивается с настоящим пакетом: у каталога `custom_components/` нет и не будет # `__init__.py` (его форму задаёт Home Assistant, а не мы). Корень пакетов назван # явно - имя модуля берётся от него, и интеграция зовётся `custom_components.torrcast`, # ровно как её зовут тесты и сам Home Assistant. `scripts` в том же списке потому, что # явный корень отменяет прежний неявный: прогонялки сценария зовут друг друга по голому # имени (`import poolreplay`), и без этой записи их импорты перестают находиться. mypy_path = [".", "scripts"] explicit_package_bases = true warn_unused_configs = true disallow_any_explicit = false pretty = true # Home Assistant в зависимостях torrcast не значится и значиться не будет: интеграция # живёт внутри чужого процесса и типы берёт оттуда. В венве гейта его нет, поэтому # договор соседа приезжает сюда как `Any` - и подписи наших собственных функций это не # ослабляет, они проверяются как раньше. [[tool.mypy.overrides]] # `aiohttp` тут в той же роли: его в венв тянет Home Assistant, а не torrcast. module = [ "aiohttp.*", "homeassistant.*", "pytest_homeassistant_custom_component.*", "voluptuous.*", ] ignore_missing_imports = true # Наследование от `Any` тут не небрежность, а единственный способ написать сущность # Home Assistant: `MediaPlayerEntity` и `ConfigFlow` без установленного Home Assistant # ничем иным быть не могут. [[tool.mypy.overrides]] module = ["custom_components.*", "tests.hass_integration.*"] disallow_subclassing_any = false [tool.pytest.ini_options] testpaths = ["tests"] addopts = "--strict-markers" markers = [ "ffmpeg: интеграционный тест с настоящими ffmpeg/ffprobe (гоняется последовательно)", "machine: тест с настоящими часами, сокетом или подпроцессом (гоняется последовательно)", ]