no-private-imports: приватный модуль в пути импорта не ловится (from ._private import public) #19
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Найдено ревью #13. В самом PR не чинилось: там правился разбор, а это расширение охвата, и его цена — новая волна срабатываний у консьюмеров, которую надо сначала замерить.
Что не ловится
scripts/check_no_private_imports.pyсмотрит импортируемое имя (node.names), но не путь (node.module):Пробой инкапсуляции здесь ровно тот же — приватным помечен модуль, а не символ, и импортирующий одинаково лезет во внутренности пакета. Асимметрия при этом видна невооружённым глазом:
from . import _privateпадает, аfrom ._private import x— нет, хотя второе строго сильнее.Регрессии нет: построчная регулярка тоже мимо. Но пока это не покрыто, волны долга у консьюмеров (
backend#471) считаются меньше реального, и «зелёный» после волны не означает, что приватных зависимостей не осталось.Что нужно
node.moduleуImportFromи все сегменты пути уImport, а не только последний.pkg._vendor.…: скорее всего попадают под то же правило, но проверить на реальных путях стоит.no-private-imports), когда починится.Приёмка
from ._private import public,from pkg._private.sub import x,import pkg._private.sub— FAILfrom . import public,import pkg.public.sub— чистоfrom ... import (...)#5Закрываю: сделано в #25, выпущено тегом
v0.20.0.from ._private import public,from pkg._private.sub import xиimport pkg._private.subтеперь FAIL;from . import publicиimport pkg.public.subчисты. Мутация «смотреть только последний сегмент» роняет smoke.Дельта замерена и приведена в PR — но с поправкой, которую внесло ревью. Первая редакция утверждала, что +1738 срабатываний на
/usr/lib/python3/dist-packagesэто «чужой вендоринг, ровно тот класс, ради которого правило заведено». Проверка поnode.levelи принадлежности пакету показала обратное: 96% — пакет, читающий собственные внутренности (PIL→._binary,blinker→blinker._utilities,nacl→nacl._sodium), и лишь 4% — чужие внутренности. Для C-расширений эта форма вообще безальтернативна.Поэтому обоснование в README переписано на настоящее: относительные импорты не получают поблажки не потому, что это «пробой инкапсуляции» (относительный импорт по определению не выходит за свой пакет), а потому что в наших репозиториях такого класса не возникает — соседний
no-underscore-filenamesзапрещает_*.py, и на baton не-dunder приватных модулей ноль. Отсюда и+0— структурно, а не по везению. Консьюмеру без этого хука цена высокая, и там же сказано, что точечно это не гасится:exclude:фильтрует по пути файла, а не по строке.Заодно закрыт ложняк, найденный ревью:
from _typeshed import Incomplete(идиома, рекомендованная mypy) иimport _threadкраснели на корректном коде. Заведён точечныйALLOWED_MODULESпо образцуALLOWED_MARKS— только документированные модули, правило не сужается.