-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[For discussion only]tolerate empty __init__.py in namespace package discovery #3007
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
base: master
Are you sure you want to change the base?
Changes from all commits
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 | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -530,12 +530,18 @@ def process_file(state: Dict[str, Any], root_dir: str, file: str): | |||||||||||||||||||||||||||
| # the extension and __init__.py file) | ||||||||||||||||||||||||||||
| if len(parts) == 2: | ||||||||||||||||||||||||||||
| # Ensure that we don't have a __init__.py to force this package to | ||||||||||||||||||||||||||||
| # be a NS package | ||||||||||||||||||||||||||||
| # be a NS package. Tolerate empty (0-byte) __init__.py files | ||||||||||||||||||||||||||||
| # because build systems like Bazel's rules_python auto-generate | ||||||||||||||||||||||||||||
| # them for every directory, even namespace packages. | ||||||||||||||||||||||||||||
| if parts[1] == "__init__.py": | ||||||||||||||||||||||||||||
| raise RuntimeError( | ||||||||||||||||||||||||||||
| "Package '%s' providing '%s' is not an implicit namespace " | ||||||||||||||||||||||||||||
| "package as required" % (state["name"], EXT_PKG) | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| init_path = os.path.join(root_dir, file) | ||||||||||||||||||||||||||||
| if os.path.isfile(init_path) and os.path.getsize(init_path) > 0: | ||||||||||||||||||||||||||||
| raise RuntimeError( | ||||||||||||||||||||||||||||
| "Package '%s' providing '%s' is not an implicit " | ||||||||||||||||||||||||||||
| "namespace package as required" | ||||||||||||||||||||||||||||
| % (state["name"], EXT_PKG) | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||
| # Check for any metadata; we can only have one metadata per | ||||||||||||||||||||||||||||
| # distribution at most | ||||||||||||||||||||||||||||
| if EXT_META_REGEXP.match(parts[1]) is not None: | ||||||||||||||||||||||||||||
|
|
@@ -596,7 +602,11 @@ def process_file(state: Dict[str, Any], root_dir: str, file: str): | |||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if len(parts) == len(ext_list) + 3 and ( | ||||||||||||||||||||||||||||
| EXT_CONFIG_REGEXP.match(parts[-1]) is not None | ||||||||||||||||||||||||||||
| or parts[-1] == "__init__.py" | ||||||||||||||||||||||||||||
| or ( | ||||||||||||||||||||||||||||
| parts[-1] == "__init__.py" | ||||||||||||||||||||||||||||
| and os.path.isfile(os.path.join(root_dir, *parts)) | ||||||||||||||||||||||||||||
| and os.path.getsize(os.path.join(root_dir, *parts)) > 0 | ||||||||||||||||||||||||||||
|
Comment on lines
+607
to
+608
Contributor
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. Same incorrect path for non-distribution packages in the config-module check
The correct path (consistent with line 571's os.path.join(root_dir, *parts[1:])Because |
||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
Comment on lines
+607
to
+609
Contributor
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. Duplicate path computation
Suggested change
Or more readably, extract a local variable before the outer _init_abs = os.path.join(root_dir, *parts[1:])
if len(parts) == len(ext_list) + 3 and (
EXT_CONFIG_REGEXP.match(parts[-1]) is not None
or (
parts[-1] == "__init__.py"
and os.path.isfile(_init_abs)
and os.path.getsize(_init_abs) > 0
)
): |
||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||
| parts[-1] = parts[-1][:-3] # Remove the .py | ||||||||||||||||||||||||||||
| config_module = ".".join(parts) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
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.
Incorrect path construction for non-distribution (PYTHONPATH /
.pth) packagesos.path.join(root_dir, file)produces the wrong absolute path whenprocess_fileis called from the additional-dirs walk (call site 2, line 756) or the PYTHONPATH walk (call site 3, line 871).In those two call sites,
root_diralready ends withEXT_PKG(e.g./path/to/metaflow_extensions), whilefilestarts withEXT_PKG(e.g.metaflow_extensions/__init__.py). The join therefore becomes/path/to/metaflow_extensions/metaflow_extensions/__init__.py, a path that does not exist.os.path.isfile(init_path)returnsFalse, theRuntimeErroris never raised, and the function silently returns — even when the__init__.pyis non-empty. This is a regression: the original code would have raised the error unconditionally.The rest of the function consistently uses
os.path.join(root_dir, *parts[1:])(stripping the leadingmetaflow_extensionscomponent) when it needs a real on-disk path (full_path_files, line 571). The same pattern is correct here:With
root_dir = "/path/to/metaflow_extensions"andparts = ["metaflow_extensions", "__init__.py"]:os.path.join(root_dir, *parts[1:])→/path/to/metaflow_extensions/__init__.py✓os.path.join(root_dir, file)→/path/to/metaflow_extensions/metaflow_extensions/__init__.py✗Note that for the distribution call site (line 723),
root_diris the parent ofmetaflow_extensions, soos.path.join(root_dir, *parts[1:])would give/dist_root/__init__.pywhich is also wrong for that call site. A safe approach is to probe both candidates and pick whichever exists:or alternatively, accept the absolute path as a parameter from the call sites that already walk the filesystem.