feat(no-private-imports): приватным считается любой сегмент пути импорта #25
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/private-module-in-path"
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?
Закрывает #19.
Асимметрия, которую видно невооружённым глазом
Проверялось импортируемое имя, но не путь:
Теперь приватен любой сегмент: сегменты
node.moduleуImportFrom(включая относительные) и все сегменты пути уImport.Звёздочка проверяется наполовину:
from mypkg.public import *чист, аfrom mypkg._internal import *— нарушение. Путь смотрится как обычно, а имена, приезжающие из звёздочки, — нет: что там придёт, знает только импортируемый модуль через свой__all__.Замер дельты и что он на самом деле значит
personal/batonbackend/usr/lib/python3/dist-packagesПервая редакция этого PR утверждала, что +1738 — это «чужой вендоринг, ровно тот класс, который правило и называет пробоем инкапсуляции». Это было неверно, и ревью справедливо на этом настояло. Я классифицировал каждое новое срабатывание по
node.levelи по тому, совпадает ли верхний сегмент пути с пакетом самого файла:blinker/base.py: from blinker._utilities import …PIL/PcxImagePlugin.py: from ._binary import i16leattrs/__init__.py: from attr._next_gen import asdictТо есть 96% новых срабатываний — пакет, читающий собственные внутренности, а не пробой чужой инкапсуляции. Для C-расширений эта форма вообще безальтернативна: скомпилированный модуль по конвенции зовётся
_rust/_sodium/_speedups, и pure-python обёртка обязана его импортировать.Почему относительные импорты всё же не получают поблажки
Не потому, что это «пробой» — относительный импорт по определению не выходит за пределы своего пакета, и владелец приватного модуля тут же и нарушитель. Причина другая, и она структурная: в наших репозиториях такого класса не возникает, потому что соседний хук
no-underscore-filenamesзапрещает_*.pyвовсе. На baton не-dunder_-модулей ноль штук из 1850 файлов — отсюда и +0, это не везение.Поблажка для
level > 0вернула бы ровно ту асимметрию, ради которой заводился #19:from . import _privateпадает, а строго более сильноеfrom ._private import x— нет.Консьюмеру, который включает
no-private-importsбезno-underscore-filenames, правило обойдётся дорого, и точечно это не гасится:exclude:в pre-commit фильтрует по пути файла, а не по строке — исключать придётся файл целиком. Записано в README прямым текстом, а не спрятано.Allowlist
_thread(документированный модуль stdlib) и_typeshed(from _typeshed import Incomplete— рекомендованная mypy идиома). Оба до этой правки краснели, и это был ложняк на корректном коде. Приватные модули библиотек (pip._internal,nacl._sodium) в allowlist не идут: там подчёркивание значит ровно то, что значит.Проверка
Фикстуры:
module_in_path.py,middle_segment.py,relative_module.py,star_private_path.py,public_path.py(все сегменты публичны либо в allowlist — включаяfrom __future__ import annotations,_typeshed,_threadиfrom . import sibling, гдеmoduleпуст).Мутации, каждая роняет smoke: вернуть проверку последнего сегмента у
Import, перестать смотретьnode.module, игнорировать allowlist, снять dunder-исключение. Последняя краситfrom __future__ import annotations— фикстура добавлена по ревью: dunder-предикат теперь несёт новую нагрузку (проверяет сегменты пути), и его регрессия покрасила бы буквально каждый файл консьюмера.Версия
0.18.0→0.20.0. Мержить после #24 (0.19.0) — иначе он понизит версию при мерже; конфликты по трём версионным строкам разрешу при ребейзе.6d8a6418f9d6a8d271d2Ревью (субагент, свежий контекст): код APPROVE, блокеры — по тексту и порядку мержа. Классификацию дельты воспроизвёл независимо, ревью право: 1294 своих абсолютных + 438 своих относительных + 74 чужих. Диспозиция:
Блокеры — исправлены:
uaclient…_is_attachedиз описания убран — он и старой версией ловился, в дельту не входит.exclude:продавался дешевле, чем есть). README теперь говорит прямо:exclude:фильтрует по пути файла, а не по строке, точечно такой импорт не гасится. Настоящая причина+0на baton названа: соседнийno-underscore-filenamesзапрещает_*.py, поэтому класса внутрипакетных импортов там нет структурно, а не по везению. Консьюмеру без этого хука цена высокая — так и написано.v0.20.0проставлю после мержа —v0.10.0–v0.18.0уже на remote.Ниты — исправлены:
README.md:107(«приватным делает последний сегмент») — снято, противоречило соседнему абзацу.import *уточнена в README и докстринге: путь до звёздочки проверяется, имена из неё — нет. Добавлен кейсprivate_imports_star_private_path_fail..pre-commit-hooks.yaml— description хука описывает новую семантику: консьюмер видит именно его в выводе pre-commit.fixtures/private_imports/relative.py.from __future__ import annotations— принято как есть: dunder-предикат теперь проверяет и сегменты пути, и его регрессия покрасила бы каждый файл консьюмера. Мутация «снять dunder-исключение» теперь роняет и этот кейс.middle_segmentиrelative_module._typeshed/_thread— сделано сразу, а не follow-up'ом. Это единственный класс, где краснел корректный код не своего пакета, причёмfrom _typeshed import Incomplete— идиома, рекомендованная самим mypy. Заведён точечныйALLOWED_MODULES— ровно по образцуALLOWED_MARKSвno-session-marks-in-code: только документированные значения, правило не сужается. Мутация «игнорировать allowlist» роняетprivate_imports_public_path.d6a8d271d29323ec8799