79 import cycles across 27 modules, held open by deferred imports #248

Open
opened 2026-09-16 13:48:20 +00:00 by tiagoagueda · 0 comments
Owner

Run on 2026-09-16 with the CodeQL CLI 2.27.0 (the toolchain the VS Code extension ships), pack codeql/python-queries@1.8.10, suite python-security-and-quality, over main at 23c742c7f. 131 results, 354 source files.

py/cyclic-import accounts for 79 of the 131 results, spread over 27 modules. Every one
of them is a real cycle in the module graph, and every one is held open by an import that
was moved inside a function to stop it closing at import time.

The worst of it:

Module Cycles
notifications/transport.py 10
core/mail_auth.py 5
core/phone_numbers.py 5
plugins/policy.py 5
plugins/registry.py 5
resume/translating.py 5
core/mail.py 4
documents/rendering.py 4
core/channels.py, resume/forms.py, plugins/models.py, core/models.py 3 each

notifications/transport.py alone defers ten imports: postulo.plugins.registry,
postulo.core.site (four separate times, in four functions), postulo.accounts.recovery,
postulo.core.phone_numbers, postulo.core.models, postulo.accounts.models and
postulo.plugins.installing.

This is not a bug and nothing is broken today. It is filed because the deferred import is
load-bearing: it is the only thing keeping the cycle from being an ImportError, it is
invisible at the top of the file where a reader looks for dependencies, and moving any one
of them back to module scope breaks startup in a way that no test names. core/channels.py
and notifications/transport.py import each other; so do plugins/base.py and
plugins/installing.py, and plugins/fetching.py and plugins/base.py.

Worth deciding, rather than fixing file by file:

  • whether core/site should be a leaf that nothing in core imports back, since six of the
    deferrals point at it;
  • whether the plugins package needs a layer below base.py for the things base,
    installing and fetching all want;
  • whether notifications/transport.py is doing too much, given it reaches into plugins,
    accounts and core.

A TYPE_CHECKING guard covers the deferrals that exist only for annotations; the rest are
genuine runtime dependencies and need the graph changed, not the import moved.

Full SARIF is reproducible with:

codeql database create db --language=python --source-root=.
codeql database analyze db codeql/python-queries:codeql-suites/python-security-and-quality.qls --format=sarif-latest --output=postulo.sarif
Run on 2026-09-16 with the CodeQL CLI 2.27.0 (the toolchain the VS Code extension ships), pack `codeql/python-queries@1.8.10`, suite `python-security-and-quality`, over `main` at `23c742c7f`. 131 results, 354 source files. `py/cyclic-import` accounts for 79 of the 131 results, spread over 27 modules. Every one of them is a real cycle in the module graph, and every one is held open by an import that was moved inside a function to stop it closing at import time. The worst of it: | Module | Cycles | | --- | --- | | `notifications/transport.py` | 10 | | `core/mail_auth.py` | 5 | | `core/phone_numbers.py` | 5 | | `plugins/policy.py` | 5 | | `plugins/registry.py` | 5 | | `resume/translating.py` | 5 | | `core/mail.py` | 4 | | `documents/rendering.py` | 4 | | `core/channels.py`, `resume/forms.py`, `plugins/models.py`, `core/models.py` | 3 each | `notifications/transport.py` alone defers ten imports: `postulo.plugins.registry`, `postulo.core.site` (four separate times, in four functions), `postulo.accounts.recovery`, `postulo.core.phone_numbers`, `postulo.core.models`, `postulo.accounts.models` and `postulo.plugins.installing`. This is not a bug and nothing is broken today. It is filed because the deferred import is load-bearing: it is the only thing keeping the cycle from being an `ImportError`, it is invisible at the top of the file where a reader looks for dependencies, and moving any one of them back to module scope breaks startup in a way that no test names. `core/channels.py` and `notifications/transport.py` import each other; so do `plugins/base.py` and `plugins/installing.py`, and `plugins/fetching.py` and `plugins/base.py`. Worth deciding, rather than fixing file by file: - whether `core/site` should be a leaf that nothing in `core` imports back, since six of the deferrals point at it; - whether the `plugins` package needs a layer below `base.py` for the things `base`, `installing` and `fetching` all want; - whether `notifications/transport.py` is doing too much, given it reaches into `plugins`, `accounts` and `core`. A `TYPE_CHECKING` guard covers the deferrals that exist only for annotations; the rest are genuine runtime dependencies and need the graph changed, not the import moved. Full SARIF is reproducible with: ``` codeql database create db --language=python --source-root=. codeql database analyze db codeql/python-queries:codeql-suites/python-security-and-quality.qls --format=sarif-latest --output=postulo.sarif ```
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Postulo/postulo#248
No description provided.