Let the migrations extra stand on its own - #61
Merged
Merged
Conversation
Owner
Author
|
Update: this branch's
Recommended order: merge #62 first (a red |
`py-common[migrations]` could not import `py_common.persistence.migrations` -- it raised "The SQLAlchemy asyncio module requires that the Python 'greenlet' library is installed". Importing that submodule runs persistence/__init__.py, which imported `engine` and therefore sqlalchemy.ext.asyncio, whose greenlet dependency only the `persistence` extra's sqlalchemy[asyncio] brings. The Alembic helpers are synchronous and need none of it. So the package __init__ was charging an importer for dependencies its own extra does not declare -- the same bug 0.2.1 fixed in runtime, http and telemetry, in the one package that fix did not reach. persistence/__init__.py now resolves re-exports on first access. No public name moved. Worth recording how it hid: whether it fails depends on what a fresh resolve pulls in, so CI was green on the last dependabot PR while a local run went red. extras-check resolves fresh rather than from the lockfile, which is the only reason it showed up at all -- the same blind spot candidate #3 is about, catching something by accident. Two gaps closed alongside it. extras-check now imports py_common.testing.routes and .tokens: public API a consumer uses in its own suite, each carrying its own extra, previously untested. And tests/test_lazy_exports.py covers the lazy-__init__ contract for all three packages -- every exported name resolves, an unknown one raises AttributeError, dir() matches __all__, and importing the package does not pull its heavy submodule. That last one runs in a subprocess, because this suite has already imported everything and a sys.modules check would pass on test ordering. The 0.2.1 mechanism had no direct test until now.
EdwardPham1615
force-pushed
the
fix/persistence-lazy-init
branch
from
September 27, 2026 13:15
05e01a0 to
2cdd48f
Compare
Owner
Author
|
Rebased onto |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Found while starting on the Candidate-additions plan:
py-common[migrations]cannot importpy_common.persistence.migrations.Pre-existing on
main— confirmed by reproducing it on a cleanmaincheckout before touching anything.Cause
Importing that submodule runs
persistence/__init__.py, which importedengineand thereforesqlalchemy.ext.asyncio, whosegreenletdependency only thepersistenceextra'ssqlalchemy[asyncio]brings. The Alembic helpers are synchronous and need none of it.So the package
__init__was charging an importer for dependencies its own extra does not declare — the same bug 0.2.1 fixed inruntime,httpandtelemetry, in the one package that fix did not reach.persistence/__init__.pynow resolves re-exports on first access (PEP 562). No public name moved.How it hid, which is the interesting part
Whether it fails depends on what a fresh resolve happens to pull in. CI was green on the last dependabot PR (#60) while a local run went red — plausibly a platform or resolution difference.
scripts/check-extras-isolation.shinstalls.[extra]fresh rather than from the lockfile, which is the only reason this surfaced. That is the blind spot Candidate addition #3 is about, catching something by accident.Two gaps closed alongside
extras-checknow importspy_common.testing.routes(http) andpy_common.testing.tokens(security). They are public API a consumer imports in its own test suite and each carries its own extra — previously untested, leaving a hole exactly where a helper is most likely to reach across packages. I hit that hole immediately: a first pass at Candidate #5 hadtesting/routes.pyimportingpy_common.security, and nothing would have caught it.tests/test_lazy_exports.pycovers the lazy-__init__contract for all three packages — every exported name resolves, an unknown one raisesAttributeError,dir()matches__all__, and importing the package does not pull its heavy submodule. The 0.2.1 mechanism had no direct test until now; it was verified only through a script that needs a throwaway environment per extra and so cannot run in this suite.That last test runs in a subprocess on purpose: this suite has already imported everything, so a
sys.modulescheck in-process would pass or fail on test ordering rather than on behaviour.Verification
399 passed (from 387), coverage 92.24%,
make extras-checkgreen on all twelve extra/module pairs.Mutation-checked twice:
persistence/__init__.pyto eager →extras-checkfails on[migrations]onlytest_importing_the_package_does_not_import_its_heavy_submodule[py_common.persistence]failsSequencing
This lands before the seven Candidate-addition PRs — it was blocking
make extras-checkas a verification step for all of them. Candidate #5 is parked on a local branch and will rebase onto this.