-
-
Notifications
You must be signed in to change notification settings - Fork 0
feat: opt-in bundling of pure-Python third-party dependencies #555
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fba87be
7875b88
445e1a8
dd74fd1
fbbf156
ada7b6b
604c73e
e4f66cb
bde7d8e
4ec89ce
69f01e3
bab279a
a8b118e
7304713
5f51349
bf413d4
76fcc36
878e397
511e37f
5ee98f6
f3ae20f
9a8a570
6703286
810e6ad
d59e915
bac2bcb
93a2242
c5e8055
3ee5eb6
c6bc1be
4ba766c
d7b3e43
69a3e2d
94bf16d
35a52d9
b853e42
d2051e0
4800ff3
5815e1b
4ef4402
3f0bc75
6956971
e1ed210
06439a5
cd938a8
32f8243
0164d90
7f5f03e
1a6f7cd
0c818fe
2c394df
707395e
5b56013
6f0c1e2
09b4eba
950df76
4fb495b
f82b5ce
5af0e11
5b449d4
1b0a717
bbb5b39
b6fbf7b
e32f92d
644af3d
69f50b7
c58d57e
3b45173
b6d613e
1412b91
934633f
2e4c78e
418c6b1
5ec5e1c
57c049a
ee792f0
5829b83
8794386
e2dba0d
1107077
8384b46
c4eb6db
f9e98e5
e1fec64
55ead8b
a078c4a
8bdc35a
cef657b
b079529
39dbdd2
b937816
bcb0afa
f818fee
ee2f615
fc1a6ea
06c38a5
3978f82
6da97ac
25ac043
f12ba00
729bf3a
20871c2
c66a5d1
3fd6e67
fc8eea6
4c8060e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| use ruff_python_ast::{ExprContext, Stmt}; | ||
| use ruff_python_ast::{Expr, ExprContext, Stmt}; | ||
|
|
||
| use crate::{ | ||
| ast_builder::{expressions, statements}, | ||
|
|
@@ -72,6 +72,22 @@ pub(crate) fn create_wrapper_module( | |
| ); | ||
| stmts.push(namespace_stmt); | ||
|
|
||
| // 1b. Capture the namespace OBJECT in the meta-path finder: runtime imports | ||
| // must resolve it even when user code later rebinds the bundle-global name | ||
| // (`globals()["helper"] = sentinel` before a preserved import) | ||
| stmts.push(statements::expr(expressions::call( | ||
| expressions::attribute( | ||
| expressions::name("_cribo_finder", ExprContext::Load), | ||
| "bind", | ||
| ExprContext::Load, | ||
|
Comment on lines
+80
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a wrapped module sanitizes to Useful? React with 👍 / 👎.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ Addressed in 06c38a5: |
||
| ), | ||
| vec![ | ||
| expressions::string_literal(module_name), | ||
| expressions::name(&module_var, ExprContext::Load), | ||
| ], | ||
| vec![], | ||
| ))); | ||
|
|
||
| // 2. Add the init function definition and __init__ assignment if provided | ||
| if let Some(init_body) = init_function_body { | ||
| let init_stmts = create_init_function_statements(module_name, init_func_name, init_body); | ||
|
|
@@ -108,3 +124,95 @@ pub(crate) fn create_wrapper_module_init_call(module_name: &str) -> Stmt { | |
| ), | ||
| ) | ||
| } | ||
|
|
||
| /// Wrap a bundled-module access expression with a `sys.modules` consult: | ||
| /// | ||
| /// ```python | ||
| /// _cribo.importlib.import_module("pkg.sub") \ | ||
| /// if "pkg" in _cribo.sys.modules or "pkg.sub" in _cribo.sys.modules \ | ||
| /// else <access> | ||
| /// ``` | ||
| /// | ||
| /// CPython's `_find_and_load` returns an existing `sys.modules` entry before | ||
| /// invoking any finder or loader, and resolves DOTTED names through the | ||
| /// parent's `__path__` — so a preloaded replacement of the target OR of any | ||
| /// ancestor package must route through the real machinery rather than the | ||
| /// bundled access. When no component is preloaded, the direct bundled access | ||
| /// is used. The conditional (rather than `or`) also honors falsy replacement | ||
| /// objects. | ||
| pub(crate) fn sys_modules_consult_or(module_name: &str, access: Expr) -> Expr { | ||
| use ruff_python_ast::BoolOp; | ||
|
|
||
| let cribo_attribute = |attribute: &str| { | ||
| expressions::attribute( | ||
| expressions::name(super::CRIBO_PREFIX, ExprContext::Load), | ||
| attribute, | ||
| ExprContext::Load, | ||
| ) | ||
| }; | ||
| let sys_modules = | ||
| || expressions::attribute(cribo_attribute("sys"), "modules", ExprContext::Load); | ||
|
|
||
| // "pkg" in _cribo.sys.modules or "pkg.mid" in ... or "pkg.mid.sub" in ... | ||
| let mut component_tests = Vec::new(); | ||
| let mut boundary = 0_usize; | ||
| loop { | ||
| match module_name[boundary..].find('.') { | ||
| Some(offset) => boundary += offset, | ||
| None => boundary = module_name.len(), | ||
| } | ||
| component_tests.push(expressions::in_op( | ||
| expressions::string_literal(&module_name[..boundary]), | ||
| sys_modules(), | ||
| )); | ||
| if boundary == module_name.len() { | ||
| break; | ||
| } | ||
| boundary += 1; | ||
| } | ||
| let any_component_preloaded = if component_tests.len() == 1 { | ||
| component_tests | ||
| .pop() | ||
| .expect("one component test must exist") | ||
| } else { | ||
| expressions::bool_op(BoolOp::Or, component_tests) | ||
| }; | ||
|
|
||
| expressions::if_exp( | ||
| any_component_preloaded, | ||
| expressions::call( | ||
| expressions::attribute( | ||
| cribo_attribute("importlib"), | ||
| "import_module", | ||
| ExprContext::Load, | ||
| ), | ||
| vec![expressions::string_literal(module_name)], | ||
| vec![], | ||
| ), | ||
| access, | ||
| ) | ||
| } | ||
|
|
||
| /// Creates a wrapper module initialization call that honors preloaded | ||
| /// `sys.modules` entries, for modules whose entries consumer code observably | ||
| /// manipulates: | ||
| /// | ||
| /// `module = <consult> else module.__init__(module)` | ||
| pub(crate) fn create_wrapper_module_init_call_honoring_sys_modules( | ||
| module_var: &str, | ||
| original_module_name: &str, | ||
| ) -> Stmt { | ||
| let init_call = expressions::call( | ||
| expressions::attribute( | ||
| expressions::name(module_var, ExprContext::Load), | ||
| MODULE_INIT_ATTR, | ||
| ExprContext::Load, | ||
| ), | ||
| vec![expressions::name(module_var, ExprContext::Load)], | ||
| vec![], | ||
| ); | ||
| statements::assign( | ||
| vec![expressions::name(module_var, ExprContext::Store)], | ||
| sys_modules_consult_or(original_module_name, init_call), | ||
| ) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a pure dependency installs a custom module type with
sys.modules[__name__].__class__ = CustomModule, this condition treats the self-access as safely handled by the wrapper path. The wrapper namespace is atypes.SimpleNamespace, however, so assigning aModuleTypesubclass to its__class__raisesTypeErrorbecause the object layouts differ, whereas the installed module supports the custom properties or lookup behavior. Use a real module object for this pattern or keep such providers external, with an execution snapshot.AGENTS.md reference: AGENTS.md:L245-L250
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ Addressed in 55ead8b. Thanks! (Apologies for the delayed reply on this round — it was processed together with the follow-up review.)