INW003 public-api-only¶
Co robi¶
Zgłasza import, spoza kontekstu, jednego z jego modułów, który nie jest publiczny. Moduł jest publiczny, gdy leży na jednym z prefiksów public kontekstu albo pod nim: public = ["shop.billing.api"] otwiera shop.billing.api i wszystko pod nim, co należy do tego kontekstu, a nic więcej w shop.billing.
- Wywołujący może należeć do innego kontekstu albo do żadnego, jak punkt wejścia czy skrypt, z warstwą albo bez: gdy konteksty są zadeklarowane, sprawdzane są importy każdego pliku. Tylko kod wewnątrz samego kontekstu może importować jego wnętrze.
- Dopóki INW002 jest włączona, import z kontekstu, który nie deklaruje celu w
depends-on, należy do INW002 i nie dostaje dodatkowo INW003: usunąć trzeba samą zależność, niezależnie od tego, przez który moduł prowadzi. Gdy INW002 jest wyłączona, zgłasza go INW003. - Importowana nazwa liczy się jako moduł, w którym żyje:
from shop.billing.invoices import issue_invoiceimportujeshop.billing.invoices. - Poprawka wskazuje moduł publiczny, który już udostępnia importowaną nazwę, definiując ją albo importując, jeśli taki istnieje.
Liczy się każdy import, tak jak w INW002: względny, w funkcji albo za TYPE_CHECKING i import dynamiczny, którego cel Inwards potrafi odczytać. Reguła działa niezależnie od tego, czy plik należy do warstwy.
Dlaczego to źle¶
Moduły publiczne kontekstu to obietnica, którą składa reszcie kodu; wszystko inne może się zmienić bez uprzedzenia. Wywołujący, który importuje moduł wewnętrzny, wiąże się z tym, jak kontekst jest zbudowany dziś, więc zmiana nazwy albo podział wewnątrz billing psuje orders. Agent szuka potrzebnej nazwy i importuje ją stamtąd, gdzie jest zdefiniowana, a to zwykle moduł wewnętrzny. INW003 kieruje go do publicznego.
Przykład¶
Zamówienia mogą korzystać z rozliczeń, a modułem publicznym rozliczeń jest shop.billing.api, który reeksportuje funkcję wystawiania faktury:
[tool.inwards]
layers = [
{ name = "slices", modules = ["shop.orders", "shop.billing"] },
]
[[tool.inwards.contexts]]
name = "orders"
modules = ["shop.orders"]
depends-on = ["billing"]
[[tool.inwards.contexts]]
name = "billing"
modules = ["shop.billing"]
public = ["shop.billing.api"]
shop/orders/checkout.py:1:35: INW003 Context "orders" imports "shop.billing.invoices.issue_invoice" from context "billing", but "shop.billing.invoices" isn't one of its public modules.
fix: Import "issue_invoice" from "shop.billing.api", the public module of "billing" that exposes it.
1. Replace `from shop.billing.invoices import issue_invoice` with `from shop.billing.api import issue_invoice`.
2. Do not reach the module another way (through a function-level import, TYPE_CHECKING or importlib); Inwards checks those too.
docs: https://sircypkowskyy.github.io/inwards/rules/INW003/
Po poprawce kasa importuje funkcję z publicznego modułu rozliczeń.
Jak naprawić¶
- Jeśli poprawka wskazuje moduł publiczny, zaimportuj nazwę stamtąd.
- W przeciwnym razie reeksportuj to, czego potrzebujesz, z jednego z publicznych modułów kontekstu (
from shop.billing.invoices import issue_invoicewshop/billing/api.py), a potem importuj stamtąd. To zmiana kodu, nie konfiguracji. - Jeśli kontekst nie deklaruje modułów publicznych, zapytaj użytkownika, które z jego modułów może importować inny kod. Hooki nie pozwalają agentowi edytować
[tool.inwards].
Bezpieczeństwo poprawki¶
Bez automatycznej poprawki (autofix: false). Moduł publiczny, który udostępnia tę samą nazwę, zwykle udostępnia ten sam obiekt, ale nie musi, a reeksport nazwy z modułu publicznego poszerza obietnicę kontekstu. Jedno i drugie trzeba sprawdzić.
Konfiguracja¶
Poziom: błąd. Reguła czyta public w [[tool.inwards.contexts]] i bez kontekstów nic nie robi; dokumentacja konfiguracji opisuje wszystkie klucze. Kontekst bez public trzyma wszystkie swoje moduły jako prywatne. [tool.inwards.rules] może regułę wyłączyć albo zmienić w ostrzeżenie (Konfiguracja reguł).
Pojedynczy import można wyciszyć w jego linii, z podaniem powodu:
from shop.billing.invoices import issue_invoice # inwards: ignore[INW003] reason="api.py lands in #91"
Znane ograniczenia¶
- Wpisy
publicsą prefiksami (ADR-030), więcpublic = ["shop.billing"]otwiera każdy moduł pod pakietem. Żeby fasada pakietu była publiczna bez jego podmodułów_private, zapisz wpis z wiodącym=:public = ["=shop.billing", "shop.billing.models"]otwierashop.billingorazshop.billing.models, afrom shop.billing._invoices import issue_invoicenadal jest INW003. Inwards nie sprawdza, czy fasada cokolwiek reeksportuje, ani nie traktuje nazw z prefiksem_w szczególny sposób. - Żeby wskazać moduł publiczny, poprawka czyta nazwy, które moduł wiąże na najwyższym poziomie, z jego drzewa składni:
def,class, przypisanie z wartością, import pod nazwą zas. Nazwa wymieniona tylko w__all__albo związana wewnątrz blokuifczytrynie zostanie znaleziona; import i tak jest zgłaszany, z ogólną radą. Serwer języka czyta moduł publiczny ponownie, gdy zostanie zapisany. - Import dynamiczny, którego celu Inwards nie potrafi odczytać, nie jest tu zgłaszany. W warstwie zgłasza go INW011.
Źródła¶
- INW002: które konteksty mogą importować które; INW003 decyduje, które z ich modułów.
- ADR-030: jak deklaruje się konteksty i ich moduły publiczne.