diff --git a/fluid_build/providers/odps/__init__.py b/fluid_build/providers/odps/__init__.py index 631621b0..d06c19bb 100644 --- a/fluid_build/providers/odps/__init__.py +++ b/fluid_build/providers/odps/__init__.py @@ -24,4 +24,7 @@ # DIRECTLY: from fluid_build.providers.odps.odps import OdpsProvider # (see cli/odps.py, cli/export_odps.py, cli/generate_standard.py). This package # __init__ deliberately exposes no BaseProvider subclass, so the provider -# auto-discovery scan cannot re-register it. +# auto-discovery scan cannot re-register it — and we ALSO set the explicit +# opt-out below (consistent with the odcs / odps_standard sibling packages) so a +# future refactor that re-exposed the class here can't silently re-register it. +__fluid_no_autoregister__ = True diff --git a/fluid_build/providers/odps_standard/__init__.py b/fluid_build/providers/odps_standard/__init__.py index 3131c7c6..6d9512eb 100644 --- a/fluid_build/providers/odps_standard/__init__.py +++ b/fluid_build/providers/odps_standard/__init__.py @@ -25,5 +25,11 @@ # them DIRECTLY: from fluid_build.providers.odps_standard import ( # BitolOdpsProvider, OdpsStandardProvider) # (see cli/odps.py, cli/odps_standard.py, cli/generate_standard.py). +# +# Set the opt-out EXPLICITLY (like the odps/odcs sibling packages) rather than +# relying on the accident that this module exposes two BaseProvider subclasses +# and so escapes the single-subclass auto-register fallback. Otherwise a future +# refactor that left a single exporter class here would silently re-register it. +__fluid_no_autoregister__ = True __all__ = ["BitolOdpsProvider", "OdpsStandardProvider"] diff --git a/tests/providers/test_no_exporter_providers.py b/tests/providers/test_no_exporter_providers.py index be9b5e8f..232b9988 100644 --- a/tests/providers/test_no_exporter_providers.py +++ b/tests/providers/test_no_exporter_providers.py @@ -31,6 +31,11 @@ "does not support apply", "export action acknowledged", "use render() for actual export", + # An apply() whose body is just a refusal is an exporter, not a provider — + # cover the common refusal shapes so a future sibling can't slip past with + # different wording. + "notimplementederror", + "not implemented", ) @@ -70,3 +75,22 @@ def test_odcs_exporter_still_importable_directly(): from fluid_build.providers.odcs import OdcsProvider assert hasattr(OdcsProvider(), "render") + + +def test_exporter_packages_explicitly_opt_out_of_autoregistration(): + # The exporter packages must de-register EXPLICITLY (set + # __fluid_no_autoregister__), not rely on incidental properties of their + # module namespace (e.g. exposing >1 BaseProvider subclass). Otherwise a + # refactor could silently re-register them as providers. + import importlib + + for modname in ( + "fluid_build.providers.odps", + "fluid_build.providers.odcs", + "fluid_build.providers.odps_standard", + ): + mod = importlib.import_module(modname) + assert getattr(mod, "__fluid_no_autoregister__", False) is True, ( + f"{modname} must set __fluid_no_autoregister__ = True (it is an exporter, " + "not a provider) instead of relying on auto-discovery accidents" + )