Fix PyInstaller hook for CGX files inside packages - #184
Merged
Conversation
The hook registered CGX modules under their bare file stem, so a CGX file inside a package (e.g. myapp/components/button.cgx, imported as myapp.components.button) never matched its MissingModule node in the module graph and graph.import_hook() failed silently, leaving the generated module out of the bundle. Derive the fully qualified module name by walking up the directory tree while parent directories are packages, and resolve relative imports in CGX scripts against the containing package when collecting hidden imports. The todo_app integration test now covers both cases: a flat CGX module importing a CGX module from a package, which in turn uses a relative import to a plain Python module. Fixes #178 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merged
berendkleinhaneveld
added a commit
that referenced
this pull request
Jul 21, 2026
Features: - Add pure-Python view API as alternative to cgx templates (#193) - Support text elements for PySide widgets that display text (#191) Fixes & internals: - Fragment parenting overhaul (#162) - Fix PyInstaller hook for CGX files inside packages (#184) - Write compiled AST to temp file when CGX_DEBUG is set (#175) Performance: - Speed up mount path: cheap arity check, reuse first(), leaner emit (#186) - Cache Fragment._component_parent() lookups (#187) - Avoid redundant anchor lookups in Fragment.anchor() and unkeyed v-for (#188) Documentation: - Add MkDocs documentation with GitHub Pages deployment (#176) - Add internals architecture documentation page (#194) - Add docs badge and links to README (#192) Tooling & CI: - Add benchmark suite and per-PR benchmark CI workflow (#185) - Make benchmark CI guard robust against run-to-run noise (#196) - Update GitHub actions from Node 20 to Node 24 (#190) - Migrate from pre-commit to prek (#195) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.
Fixes #178
Root cause
The PyInstaller hook registered CGX modules under their bare file stem (
cgx_path.stem). That only works when CGX files sit flat next to the entry script — which is exactly what the integration test app looked like. In a real app where CGX files live inside packages, a file likemyapp/components/button.cgxis imported asmyapp.components.button, but the hook looked for a graph node named justbutton. TheMissingModulenode was never cleaned up,graph.import_hook("button", ...)raised anImportErrorthat was silently swallowed, and the compiled module never made it into the bundle.The spec-file workaround from #178 worked because pre-generated
.pyfiles go through PyInstaller's normal analysis, which resolves package-qualified names correctly. So the files weren't generated too late — they were registered under the wrong names.Changes
qualified_module_name()derives the fully qualified module name by walking up the directory tree while parent directories are packages (contain__init__.py), and is used everywhere the hook previously used the bare stem.ImportsCollectornow resolves relative imports (from .todo_item import ...) in CGX scripts against the containing package instead of emitting them as bogus top-level hidden imports.Tests
Restructured
tests/pyinstaller/todo_appso the existing integration test guards the regression:todo_item.cgxandhelpers.pymoved into acomponentspackage whiletodo_list.cgxstays flat. One build now exercises a flat CGX module, an absolute import of a packaged CGX module, and a relative import of a plain Python module.Verified locally: reproduced the failure (
ModuleNotFoundError: No module named 'myapp.components.todo_list') with a package-structured app before the fix, and confirmed the same bundle runs correctly after.test_pyinstaller.shpasses, as do the unit tests and ruff.Known limitation
Qualified-name detection relies on
__init__.pyfiles, so CGX files inside namespace packages (no__init__.py) still fall back to the old bare-name behavior.🤖 Generated with Claude Code