Skip to content

Commit 9193237

Browse files
Fix PyInstaller hook for CGX files inside packages
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>
1 parent dc6b294 commit 9193237

5 files changed

Lines changed: 35 additions & 7 deletions

File tree

collagraph/__pyinstaller/hook-collagraph.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ def _cleanup_generated_files():
1919
atexit.register(_cleanup_generated_files)
2020

2121

22+
def qualified_module_name(path):
23+
"""Return the fully qualified module name for the given file
24+
by walking up the directory tree for as long as the directories
25+
are packages (contain an __init__.py file)."""
26+
parts = [path.stem]
27+
directory = path.parent
28+
while (directory / "__init__.py").exists():
29+
parts.insert(0, directory.name)
30+
directory = directory.parent
31+
return ".".join(parts)
32+
33+
2234
def hook(hook_api):
2335
collagraph_uses = hook_api.analysis.graph.get_code_using("collagraph")
2436

@@ -37,7 +49,7 @@ def hook(hook_api):
3749
# removing the need to bundle .cgx files and compile them
3850
# at runtime
3951
for cgx_path in cgx_files:
40-
module_name = cgx_path.stem
52+
module_name = qualified_module_name(cgx_path)
4153
tree, _name = construct_ast(cgx_path)
4254
python_source = ast.unparse(tree)
4355

@@ -104,8 +116,10 @@ def collect_hidden_imports(cgx_files):
104116
# Get the AST from the script tag
105117
script_tree = get_script_ast(parser, path)
106118

107-
# Find a list of imported module names
108-
imported_names = ImportsCollector()
119+
# Find a list of imported module names, resolving relative
120+
# imports against the package that contains the CGX file
121+
package, _, _ = qualified_module_name(path).rpartition(".")
122+
imported_names = ImportsCollector(package)
109123
imported_names.visit(script_tree)
110124

111125
hidden_imports |= imported_names.names
@@ -114,12 +128,26 @@ def collect_hidden_imports(cgx_files):
114128

115129

116130
class ImportsCollector(ast.NodeVisitor):
117-
def __init__(self):
131+
def __init__(self, package=""):
132+
self.package = package
118133
self.names = set()
119134

120135
def visit_ImportFrom(self, node):
136+
if node.level == 0:
137+
if node.module:
138+
self.names.add(node.module)
139+
return
140+
141+
# Resolve relative import against the containing package
142+
parts = self.package.split(".") if self.package else []
143+
if node.level - 1 > len(parts):
144+
return
145+
base = parts[: len(parts) - (node.level - 1)]
121146
if node.module:
122-
self.names.add(node.module)
147+
self.names.add(".".join([*base, node.module]))
148+
else:
149+
for alias in node.names:
150+
self.names.add(".".join([*base, alias.name]))
123151

124152
def visit_Import(self, node):
125153
for alias in node.names:

tests/pyinstaller/todo_app/components/__init__.py

Whitespace-only changes.
File renamed without changes.

tests/pyinstaller/todo_app/todo_item.cgx renamed to tests/pyinstaller/todo_app/components/todo_item.cgx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<script>
44
import collagraph as cg
5-
from helpers import format_label
5+
from .helpers import format_label
66

77

88
class TodoItem(cg.Component):

tests/pyinstaller/todo_app/todo_list.cgx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
<script>
1111
import collagraph as cg
12-
from todo_item import TodoItem
12+
from components.todo_item import TodoItem
1313

1414

1515
class TodoList(cg.Component):

0 commit comments

Comments
 (0)