From e68349f0a721153988ae8a5c4bf4631033dad361 Mon Sep 17 00:00:00 2001 From: Tom Kaltofen Date: Mon, 13 Jul 2026 18:46:06 +0000 Subject: [PATCH 1/2] chore(deps): bump mloda to 0.10.0 Raise the mloda floor from 0.9.0 to 0.10.0 and refresh the lock. Improvements surfaced by the bump: - MyComputeFramework used `uuid: UUID = uuid4()` as a default. Python evaluates a default once at import, so every instance shared a single uuid. mloda keys compute-framework instances by uuid, so any plugin generated from this template inherited the collision. Default to None and mint a fresh uuid per instance instead. - Modernize the signature to `X | None` per the repo type-hint rule. - Bump the stale mloda version example in the issue template. 0.10.0 needs no migration here: the breaking changes (PROPERTY_MAPPING flattened form, input-feature option forwarding, allow_empty_result) do not touch the template's surface. Full tox gate and pip-audit are green. --- .github/ISSUE_TEMPLATE/issue.yml | 2 +- .../my_plugin/my_compute_framework.py | 9 +++++---- .../my_plugin/tests/test_my_compute_framework.py | 13 +++++++++++++ pyproject.toml | 2 +- uv.lock | 6 +++--- 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/issue.yml b/.github/ISSUE_TEMPLATE/issue.yml index dd84824..12c0bef 100644 --- a/.github/ISSUE_TEMPLATE/issue.yml +++ b/.github/ISSUE_TEMPLATE/issue.yml @@ -45,4 +45,4 @@ body: attributes: label: Environment (optional, bugs only) description: Python version, OS, mloda version. - placeholder: Python 3.12, Linux, mloda 0.9.0 + placeholder: Python 3.12, Linux, mloda 0.10.0 diff --git a/placeholder/compute_frameworks/my_plugin/my_compute_framework.py b/placeholder/compute_frameworks/my_plugin/my_compute_framework.py index 1cb5df4..55f6043 100644 --- a/placeholder/compute_frameworks/my_plugin/my_compute_framework.py +++ b/placeholder/compute_frameworks/my_plugin/my_compute_framework.py @@ -1,6 +1,5 @@ """Example ComputeFramework implementation.""" -from typing import Optional, Set from uuid import UUID, uuid4 from mloda.core.abstract_plugins.components.parallelization_modes import ParallelizationMode @@ -15,8 +14,10 @@ def __init__( self, mode: ParallelizationMode = ParallelizationMode.SYNC, children_if_root: frozenset[UUID] = frozenset(), - uuid: UUID = uuid4(), - function_extender: Optional[Set[Extender]] = None, + uuid: UUID | None = None, + function_extender: set[Extender] | None = None, ) -> None: """Initialize with default values for minimal instantiation.""" - super().__init__(mode, children_if_root, uuid, function_extender) + # uuid defaults to None, not uuid4(): a default is evaluated once, so every + # instance would otherwise share one id. + super().__init__(mode, children_if_root, uuid or uuid4(), function_extender) diff --git a/placeholder/compute_frameworks/my_plugin/tests/test_my_compute_framework.py b/placeholder/compute_frameworks/my_plugin/tests/test_my_compute_framework.py index 3c10722..9e4dbfe 100644 --- a/placeholder/compute_frameworks/my_plugin/tests/test_my_compute_framework.py +++ b/placeholder/compute_frameworks/my_plugin/tests/test_my_compute_framework.py @@ -1,5 +1,7 @@ """Tests for MyComputeFramework.""" +from uuid import uuid4 + from placeholder.compute_frameworks.my_plugin import MyComputeFramework from mloda.provider import ComputeFramework @@ -13,3 +15,14 @@ def test_instantiation() -> None: """MyComputeFramework should instantiate with no arguments.""" instance = MyComputeFramework() assert instance is not None + + +def test_default_uuid_is_per_instance() -> None: + """Each instance gets its own uuid; a uuid4() default would be shared by all.""" + assert MyComputeFramework().uuid != MyComputeFramework().uuid + + +def test_explicit_uuid_is_respected() -> None: + """An explicitly passed uuid is not overwritten.""" + given = uuid4() + assert MyComputeFramework(uuid=given).uuid == given diff --git a/pyproject.toml b/pyproject.toml index 0e9c803..fe1f994 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ version = "0.4.0" description = "TEMPLATE PLACEHOLDER - Do not install directly. This package reserves the name for mloda-plugin-template. See https://github.com/mloda-ai/mloda-plugin-template" license = "Apache-2.0" authors = [{ name = "Your Name placeholder", email = "placeholder@placeholder.com" }] -dependencies = ["mloda>=0.9.0", "mloda-testing>=0.3.2"] +dependencies = ["mloda>=0.10.0", "mloda-testing>=0.3.2"] requires-python = ">=3.10" # mloda 0.9 discovers installed plugins through these entry points: each resolves to a diff --git a/uv.lock b/uv.lock index 882825c..7abcd5e 100644 --- a/uv.lock +++ b/uv.lock @@ -233,10 +233,10 @@ wheels = [ [[package]] name = "mloda" -version = "0.9.0" +version = "0.10.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/82/a7893ddd9ab26dd839671c594e9c16437364a8b8ed5dffb02e9b2de52d7f/mloda-0.9.0-py3-none-any.whl", hash = "sha256:d193b03622e6c8670ccde500c9e6d74eba9ef4ed70ca3bd6adbcb9330a370585", size = 430826, upload-time = "2026-07-07T08:48:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/d6/9d/2251f947bfde5e168b9f6759079c8b54af0393c3e854987268459b860776/mloda-0.10.0-py3-none-any.whl", hash = "sha256:13fd1482566de203cac661c29f5736423ce7d1cc8e43ec26f8d24d19e6386229", size = 456550, upload-time = "2026-07-13T18:35:02.322Z" }, ] [[package]] @@ -360,7 +360,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "bandit", marker = "extra == 'dev'" }, - { name = "mloda", specifier = ">=0.9.0" }, + { name = "mloda", specifier = ">=0.10.0" }, { name = "mloda-testing", specifier = ">=0.3.2" }, { name = "mypy", marker = "extra == 'dev'" }, { name = "pytest", marker = "extra == 'dev'" }, From f8117a717724ddce736c5b21adf422808f5903b4 Mon Sep 17 00:00:00 2001 From: Tom Kaltofen Date: Mon, 13 Jul 2026 18:49:35 +0000 Subject: [PATCH 2/2] test: isolate the plugin registry, modernize the extender hints Adopt the isolated_plugin_registry fixture that mloda 0.10 ships as a pytest plugin (auto-registered, no conftest wiring). The entry-point test called PluginLoader.all(force_reload=True) and mutated the process-global registry with no teardown, leaking into every later test. Also drop the last typing.Set from the example extender, and remove the now-stale "mloda 0.9" version strings from the pyproject comment and README so they cannot drift from the declared floor. --- README.md | 2 +- placeholder/extenders/my_plugin/my_extender.py | 4 ++-- pyproject.toml | 2 +- tests/test_entry_points.py | 10 +++++++--- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 203fbaf..e9901b1 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ placeholder/ ### Plugin discovery -mloda 0.9+ auto-discovers installed plugins via `PluginLoader.all()`, no manual import needed. Each +mloda auto-discovers installed plugins via `PluginLoader.all()`, no manual import needed. Each `manifest.py` lists its concrete classes (`FEATURE_GROUPS`, `COMPUTE_FRAMEWORKS`, `EXTENDERS`) and `pyproject.toml` wires them up under `[project.entry-points."mloda.*"]`. Add a plugin by appending it to the relevant list. diff --git a/placeholder/extenders/my_plugin/my_extender.py b/placeholder/extenders/my_plugin/my_extender.py index f0d761d..4ec2d20 100644 --- a/placeholder/extenders/my_plugin/my_extender.py +++ b/placeholder/extenders/my_plugin/my_extender.py @@ -1,6 +1,6 @@ """Example Extender implementation.""" -from typing import Any, Set +from typing import Any from mloda.core.abstract_plugins.function_extender import Extender, ExtenderHook @@ -8,7 +8,7 @@ class MyExtender(Extender): """Example Extender - rename and customize for your use case.""" - def wraps(self) -> Set[ExtenderHook]: + def wraps(self) -> set[ExtenderHook]: """Return the set of hooks this extender wraps.""" return set() diff --git a/pyproject.toml b/pyproject.toml index fe1f994..f5104f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ authors = [{ name = "Your Name placeholder", email = "placeholder@placeholder.co dependencies = ["mloda>=0.10.0", "mloda-testing>=0.3.2"] requires-python = ">=3.10" -# mloda 0.9 discovers installed plugins through these entry points: each resolves to a +# mloda discovers installed plugins through these entry points: each resolves to a # manifest list of concrete classes. customize.sh rewrites the dotted paths on rename. [project.entry-points."mloda.feature_groups"] placeholder = "placeholder.feature_groups.manifest:FEATURE_GROUPS" diff --git a/tests/test_entry_points.py b/tests/test_entry_points.py index 191fd28..e81a61c 100644 --- a/tests/test_entry_points.py +++ b/tests/test_entry_points.py @@ -24,8 +24,12 @@ def test_entry_points_declared() -> None: assert any(v.endswith(suffix) for v in values), f"{group}: no entry point ending in {suffix}; got {values}" -def test_plugin_loader_discovers_example_classes() -> None: - """PluginLoader.all() registers the example classes via entry points, without importing the package.""" +def test_plugin_loader_discovers_example_classes(isolated_plugin_registry: PluginRegistry) -> None: + """PluginLoader.all() registers the example classes via entry points, without importing the package. + + isolated_plugin_registry (shipped by mloda as a pytest plugin) restores the process-global + registry on teardown, so this force_reload does not leak into later tests. + """ PluginLoader.all(force_reload=True) - registered = {cls.__name__ for cls in PluginRegistry.default().registered_classes()} + registered = {cls.__name__ for cls in isolated_plugin_registry.registered_classes()} assert {"MyFeatureGroup", "MyComputeFramework", "MyExtender"} <= registered