-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Fix #3011: Handle overlapping submodule promotions gracefully in metaflow_extensions #3013
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
Open
Mustafa11300
wants to merge
5
commits into
Netflix:master
Choose a base branch
from
Mustafa11300:fix/handle-overlapping-submodule-promotions
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 3 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
969d603
Fix #3011: Handle overlapping submodule promotions gracefully
Mustafa11300 9cd4fa3
Update metaflow/extension_support/__init__.py
Mustafa11300 631a53a
Update metaflow/extension_support/__init__.py
Mustafa11300 6afa68d
Update metaflow/extension_support/__init__.py
Mustafa11300 154be83
Merge branch 'master' into fix/handle-overlapping-submodule-promotions
Mustafa11300 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
57 changes: 57 additions & 0 deletions
57
examples/overlapping_submodule_promotions/01_single_extension_no_conflict.py
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| #!/usr/bin/env python3 | ||
| """ | ||
| Example 1 – Single Extension, No Conflict | ||
| ========================================== | ||
|
|
||
| This is the baseline scenario: a single extension package promotes a | ||
| submodule and everything works without warnings. | ||
|
|
||
| What you should see when you run this script: | ||
| ✅ No warnings emitted. | ||
| ✅ The alias is registered correctly. | ||
| """ | ||
|
|
||
| import sys | ||
| import os | ||
| import types | ||
| import warnings | ||
|
|
||
| # Ensure the repo root is on the path so we can import metaflow | ||
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) | ||
|
|
||
| from metaflow.extension_support import ( | ||
| EXT_PKG, | ||
| alias_submodules, | ||
| _promoted_aliases, | ||
| ) | ||
|
|
||
|
|
||
| def main(): | ||
| # Clean slate | ||
| _promoted_aliases.clear() | ||
|
|
||
| # Simulate a single extension: metaflow_extensions.acme_corp.plugins | ||
| # that promotes "datatools" so it becomes available as | ||
| # metaflow.plugins.datatools → metaflow_extensions.acme_corp.plugins.datatools | ||
| mod = types.ModuleType("%s.acme_corp.plugins" % EXT_PKG) | ||
| mod.__package__ = mod.__name__ | ||
| mod.__mf_promote_submodules__ = ["datatools"] | ||
|
|
||
| with warnings.catch_warnings(record=True) as caught: | ||
| warnings.simplefilter("always") | ||
| aliases = alias_submodules(mod, "acme_corp", "plugins") | ||
|
|
||
| print("=== Example 1: Single Extension, No Conflict ===\n") | ||
| print("Aliases created:", list(aliases.keys())) | ||
| print("Warnings emitted:", len(caught)) | ||
|
|
||
| if len(caught) == 0: | ||
| print("\n✅ No conflict – everything is clean.") | ||
| else: | ||
| print("\n❌ Unexpected warnings!") | ||
| for w in caught: | ||
| print(" ", w.message) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
70 changes: 70 additions & 0 deletions
70
examples/overlapping_submodule_promotions/02_two_extensions_overlap_warning.py
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| #!/usr/bin/env python3 | ||
| """ | ||
| Example 2 – Two Extensions Promote the Same Alias (Overlap Warning) | ||
| ==================================================================== | ||
|
|
||
| Two different extension packages both promote ``datatools`` under | ||
| ``metaflow.plugins``. The extension system detects the conflict, | ||
| emits a ``UserWarning``, and lets the **last-loaded** package win. | ||
|
|
||
| What you should see when you run this script: | ||
| ⚠️ One warning identifying the conflicting packages. | ||
| ✅ The second package's target is the resolved alias. | ||
| """ | ||
|
|
||
| import sys | ||
| import os | ||
| import types | ||
| import warnings | ||
|
|
||
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) | ||
|
|
||
| from metaflow.extension_support import ( | ||
| EXT_PKG, | ||
| alias_submodules, | ||
| _promoted_aliases, | ||
| ) | ||
|
|
||
|
|
||
| def _make_module(org_name): | ||
| """Create a fake extension config module for *org_name*.""" | ||
| mod = types.ModuleType("%s.%s.plugins" % (EXT_PKG, org_name)) | ||
| mod.__package__ = mod.__name__ | ||
| mod.__mf_promote_submodules__ = ["datatools"] | ||
| return mod | ||
|
|
||
|
|
||
| def main(): | ||
| _promoted_aliases.clear() | ||
|
|
||
| mod_alpha = _make_module("alpha_corp") | ||
| mod_beta = _make_module("beta_corp") | ||
|
|
||
| all_warnings = [] | ||
|
|
||
| # --- First extension loads (alpha_corp) --- | ||
| with warnings.catch_warnings(record=True) as w1: | ||
| warnings.simplefilter("always") | ||
| alias_submodules(mod_alpha, "alpha_corp", "plugins") | ||
| all_warnings.extend(w1) | ||
|
|
||
| # --- Second extension loads (beta_corp) – overlap! --- | ||
| with warnings.catch_warnings(record=True) as w2: | ||
| warnings.simplefilter("always") | ||
| aliases = alias_submodules(mod_beta, "beta_corp", "plugins") | ||
| all_warnings.extend(w2) | ||
|
|
||
| print("=== Example 2: Two Extensions Overlap ===\n") | ||
| print("Final alias target:", aliases.get("metaflow.plugins.datatools")) | ||
| print("Warnings emitted:", len(all_warnings)) | ||
|
|
||
| for w in all_warnings: | ||
| print("\n⚠️ WARNING:", w.message) | ||
|
|
||
| winner = _promoted_aliases.get("metaflow.plugins.datatools") | ||
| if winner: | ||
| print("\n✅ Winner (last-loaded):", winner[0], "→", winner[1]) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
68 changes: 68 additions & 0 deletions
68
examples/overlapping_submodule_promotions/03_partial_overlap.py
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| #!/usr/bin/env python3 | ||
| """ | ||
| Example 3 – Partial Overlap | ||
| ============================ | ||
|
|
||
| Two extension packages promote several submodules each, but only **one** | ||
| alias overlaps. The system should warn about the overlapping alias only, | ||
| leaving the unique ones untouched. | ||
|
|
||
| What you should see when you run this script: | ||
| ⚠️ One warning (for ``common_utils`` only). | ||
| ✅ All other aliases are registered without warnings. | ||
| """ | ||
|
|
||
| import sys | ||
| import os | ||
| import types | ||
| import warnings | ||
|
|
||
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) | ||
|
|
||
| from metaflow.extension_support import ( | ||
| EXT_PKG, | ||
| alias_submodules, | ||
| _promoted_aliases, | ||
| ) | ||
|
|
||
|
|
||
| def main(): | ||
| _promoted_aliases.clear() | ||
|
|
||
| # alpha_corp promotes: common_utils, alpha_special | ||
| mod_alpha = types.ModuleType("%s.alpha_corp.plugins" % EXT_PKG) | ||
| mod_alpha.__package__ = mod_alpha.__name__ | ||
| mod_alpha.__mf_promote_submodules__ = ["common_utils", "alpha_special"] | ||
|
|
||
| # beta_corp promotes: common_utils, beta_special | ||
| mod_beta = types.ModuleType("%s.beta_corp.plugins" % EXT_PKG) | ||
| mod_beta.__package__ = mod_beta.__name__ | ||
| mod_beta.__mf_promote_submodules__ = ["common_utils", "beta_special"] | ||
|
|
||
| all_warnings = [] | ||
|
|
||
| with warnings.catch_warnings(record=True) as w: | ||
| warnings.simplefilter("always") | ||
| aliases_a = alias_submodules(mod_alpha, "alpha_corp", "plugins") | ||
| all_warnings.extend(w) | ||
|
|
||
| with warnings.catch_warnings(record=True) as w: | ||
| warnings.simplefilter("always") | ||
| aliases_b = alias_submodules(mod_beta, "beta_corp", "plugins") | ||
| all_warnings.extend(w) | ||
|
|
||
| print("=== Example 3: Partial Overlap ===\n") | ||
| print("Aliases from alpha_corp:", list(aliases_a.keys())) | ||
| print("Aliases from beta_corp:", list(aliases_b.keys())) | ||
| print("Total warnings:", len(all_warnings)) | ||
|
|
||
| for w in all_warnings: | ||
| print("\n⚠️ WARNING:", w.message) | ||
|
|
||
| print("\nFinal promoted aliases:") | ||
| for alias, (pkg, target) in sorted(_promoted_aliases.items()): | ||
| print(" %s → %s (from %s)" % (alias, target, pkg)) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
62 changes: 62 additions & 0 deletions
62
examples/overlapping_submodule_promotions/04_three_way_overlap.py
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| #!/usr/bin/env python3 | ||
| """ | ||
| Example 4 – Three-Way Overlap | ||
| ============================== | ||
|
|
||
| Three different extension packages all promote the same ``shared`` alias. | ||
| The system emits **two** warnings (one each time a new package overrides | ||
| the previous winner) and the very last package wins. | ||
|
|
||
| What you should see when you run this script: | ||
| ⚠️ Two warnings. | ||
| ✅ The third package is the final winner. | ||
| """ | ||
|
|
||
| import sys | ||
| import os | ||
| import types | ||
| import warnings | ||
|
|
||
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) | ||
|
|
||
| from metaflow.extension_support import ( | ||
| EXT_PKG, | ||
| alias_submodules, | ||
| _promoted_aliases, | ||
| ) | ||
|
|
||
|
|
||
| def _make_module(org_name): | ||
| mod = types.ModuleType("%s.%s.plugins" % (EXT_PKG, org_name)) | ||
| mod.__package__ = mod.__name__ | ||
| mod.__mf_promote_submodules__ = ["shared"] | ||
| return mod | ||
|
|
||
|
|
||
| def main(): | ||
| _promoted_aliases.clear() | ||
|
|
||
| packages = ["alpha_corp", "beta_corp", "gamma_corp"] | ||
| all_warnings = [] | ||
|
|
||
| for org in packages: | ||
| mod = _make_module(org) | ||
| with warnings.catch_warnings(record=True) as w: | ||
| warnings.simplefilter("always") | ||
| alias_submodules(mod, org, "plugins") | ||
| all_warnings.extend(w) | ||
|
|
||
| print("=== Example 4: Three-Way Overlap ===\n") | ||
| print("Packages loaded (in order):", packages) | ||
| print("Total warnings:", len(all_warnings)) | ||
|
|
||
| for i, w in enumerate(all_warnings, 1): | ||
| print("\n⚠️ Warning #%d: %s" % (i, w.message)) | ||
|
|
||
| winner = _promoted_aliases.get("metaflow.plugins.shared") | ||
| if winner: | ||
| print("\n✅ Final winner: %s → %s" % (winner[0], winner[1])) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
53 changes: 53 additions & 0 deletions
53
examples/overlapping_submodule_promotions/05_same_package_repromotion.py
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| #!/usr/bin/env python3 | ||
| """ | ||
| Example 5 – Same Package Re-promotion (No Warning) | ||
| ==================================================== | ||
|
|
||
| If the *same* package promotes the same alias more than once (e.g. because | ||
| the module is loaded twice), no warning should be raised – there is no | ||
| actual conflict. | ||
|
|
||
| What you should see when you run this script: | ||
| ✅ Zero warnings. | ||
| """ | ||
|
|
||
| import sys | ||
| import os | ||
| import types | ||
| import warnings | ||
|
|
||
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) | ||
|
|
||
| from metaflow.extension_support import ( | ||
| EXT_PKG, | ||
| alias_submodules, | ||
| _promoted_aliases, | ||
| ) | ||
|
|
||
|
|
||
| def main(): | ||
| _promoted_aliases.clear() | ||
|
|
||
| mod = types.ModuleType("%s.acme_corp.plugins" % EXT_PKG) | ||
| mod.__package__ = mod.__name__ | ||
| mod.__mf_promote_submodules__ = ["datatools"] | ||
|
|
||
| with warnings.catch_warnings(record=True) as caught: | ||
| warnings.simplefilter("always") | ||
| # Promote twice from the same package | ||
| alias_submodules(mod, "acme_corp", "plugins") | ||
| alias_submodules(mod, "acme_corp", "plugins") | ||
|
|
||
| print("=== Example 5: Same Package Re-promotion ===\n") | ||
| print("Warnings emitted:", len(caught)) | ||
|
|
||
| if len(caught) == 0: | ||
| print("✅ No conflict – same package re-promoting is harmless.") | ||
| else: | ||
| print("❌ Unexpected warnings!") | ||
| for w in caught: | ||
| print(" ", w.message) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| # Overlapping Submodule Promotions in `metaflow_extensions` | ||
|
|
||
| This directory contains examples that demonstrate how the Metaflow extension | ||
| system now handles **overlapping submodule promotions** – the situation where | ||
| two or more extension packages promote the same alias via | ||
| `__mf_promote_submodules__`. | ||
|
|
||
| ## Background | ||
|
|
||
| Metaflow extensions are Python packages under the `metaflow_extensions` | ||
| namespace. Each extension can *promote* submodules so they become accessible | ||
| under a short `metaflow.*` alias (e.g. `metaflow.plugins.datatools` → | ||
| `metaflow_extensions.my_org.plugins.datatools`). | ||
|
|
||
| The promotion is declared by setting `__mf_promote_submodules__` in the | ||
| extension's config module: | ||
|
|
||
| ```python | ||
| # metaflow_extensions/my_org/plugins/mfextinit_my_org.py | ||
| __mf_promote_submodules__ = ["datatools"] | ||
| ``` | ||
|
|
||
| ### The Problem (Issue #3011) | ||
|
|
||
| Before this fix, if **two** extension packages both promoted the same alias | ||
| (e.g. both declared `["datatools"]`), the conflict was handled **silently** – | ||
| the second package's target would simply overwrite the first one with no | ||
| indication to the user. | ||
|
|
||
| ### The Fix | ||
|
|
||
| The extension system now: | ||
|
|
||
| 1. **Detects** overlapping promotions from *different* packages. | ||
| 2. **Emits a `UserWarning`** that identifies both the overriding and overridden | ||
| packages, the alias in question, and the concrete target modules. | ||
| 3. **Resolves deterministically**: the **last-loaded** package wins. Load order | ||
| is determined by topological sort of package dependencies, then alphabetical | ||
| order for ties – exactly as it was before, but now *documented* and *visible*. | ||
| 4. If the **same** package re-promotes the same alias (e.g. loaded twice), no | ||
| warning is emitted because there is no actual conflict. | ||
|
|
||
| ## Examples in this Directory | ||
|
|
||
| | File | Description | | ||
| |------|-------------| | ||
| | `01_single_extension_no_conflict.py` | Baseline: a single extension promoting submodules works cleanly. | | ||
| | `02_two_extensions_overlap_warning.py` | Two extensions promote the same alias; demonstrates the warning. | | ||
| | `03_partial_overlap.py` | Two extensions with only *some* overlapping aliases. | | ||
| | `04_three_way_overlap.py` | Three extensions competing for the same alias; two warnings are emitted. | | ||
| | `05_same_package_repromotion.py` | Same package promoting twice – no warning. | | ||
|
|
||
| ## How to Run | ||
|
|
||
| Each example is a standalone Python script. Run from the repository root: | ||
|
|
||
| ```bash | ||
| python3 examples/overlapping_submodule_promotions/01_single_extension_no_conflict.py | ||
| ``` | ||
|
|
||
| > **Note:** These examples use the *internal* `alias_submodules` API with | ||
| > synthetic (fake) modules. They are designed to be educational – they show | ||
| > exactly what happens inside the extension system without requiring you to | ||
| > install actual extension packages. |
Oops, something went wrong.
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.
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.
Examples import and mutate a private internal variable
All five example scripts import the private
_promoted_aliasesdict directly and call.clear()on it to reset state between demonstrations. This pattern:get_promoted_aliases()accessor that was introduced specifically for safe access.The same concern applies to the same import pattern in
02_two_extensions_overlap_warning.py,03_partial_overlap.py,04_three_way_overlap.py, and05_same_package_repromotion.py.For reading the final state, the examples should use the public accessor. For clearing state between demos, consider either structuring each demo as a fresh subprocess call, or at minimum adding a prominent comment explaining that this is internal API only and must not be done in user code.