What you pay for, what Discord drops, and pixiv switched off #251

Merged
bvandeusen merged 37 commits from dev into main 2026-09-13 12:09:28 -04:00
Showing only changes of commit 2862fadcb1 - Show all commits
+23 -12
View File
@@ -282,24 +282,32 @@ def _first_party_imports(path: Path) -> set[str]:
"""Every `backend.app.*` module this file imports, as a dotted path
relative to `backend/app` — absolute and relative forms both."""
tree = ast.parse(path.read_text())
here = path.relative_to(_APP).with_suffix("").parts
if here and here[-1] == "__init__":
# A package's `__init__` IS the package, so `from .x import y` inside it
# resolves one level shallower than the file path suggests. Getting this
# wrong silently under-resolves every relative import in every package
# and would make the guard below unable to fail.
here = here[:-1]
# The dotted parts of the package CONTAINING this module. `parts[:-1]` is
# right for both forms without a special case: `services/foo.py` drops
# `foo` to leave `services`, and `api/__init__.py` drops `__init__` to
# leave `api` — which is exactly what `from .` means inside each.
pkg = path.relative_to(_APP).with_suffix("").parts[:-1]
out: set[str] = set()
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom):
if node.level:
# `from ..models import X` -> walk up from this module's package
base = list(here[: len(here) - node.level])
mod = base + (node.module.split(".") if node.module else [])
# `from ..models import X` -> walk up from the containing
# package. Clamped at 0: a level that climbs past `backend.app`
# leaves this tree, and an unclamped negative index would wrap
# and silently resolve to the wrong module.
up = max(len(pkg) - (node.level - 1), 0)
mod = list(pkg[:up]) + (node.module.split(".") if node.module else [])
elif node.module and node.module.startswith("backend.app."):
mod = node.module[len("backend.app."):].split(".")
else:
continue
if not mod:
# `from . import x` in a module sitting directly under
# `backend/app` (celery_app.py does this): the package is the
# app root, so the alias alone is the module's dotted name.
for alias in node.names:
out.add(alias.name)
continue
out.add(".".join(mod))
# `from .membership_roster import x` and
# `from . import membership_roster` must both resolve to the module.
@@ -320,8 +328,11 @@ def _reachable_from(roots: list[str]) -> set[str]:
if mod in seen:
continue
seen.add(mod)
for candidate in (_APP / Path(*mod.split(".")) / "__init__.py",
_APP / Path(*mod.split(".")).with_suffix(".py")):
parts = [p for p in mod.split(".") if p]
if not parts:
continue
for candidate in (_APP.joinpath(*parts) / "__init__.py",
_APP.joinpath(*parts).with_suffix(".py")):
if candidate.is_file():
queue.extend(_first_party_imports(candidate) - seen)
break