Repository: Galileo-Galilei/kedro-mlflow
Pull request #597 (2024-10-07 21:38:44)

⬆️ Update pre-commit requirement from <4.0.0,>=2.0.0 to >=2.0.0,<5.0.0
Updates the requirements on [pre-commit](https://github.com/pre-commit/pre-commit) to permit the latest version.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/pre-commit/pre-commit/releases">pre-commit's releases</a>.</em></p>
<blockquote>
<h2>pre-commit v4.0.0</h2>
<h3>Features</h3>
<ul>
<li>Improve <code>pre-commit migrate-config</code> to handle more yaml formats.
<ul>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3301">#3301</a> PR by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
</ul>
</li>
<li>Handle <code>stages</code> deprecation in <code>pre-commit migrate-config</code>.
<ul>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3302">#3302</a> PR by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/2732">#2732</a> issue by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
</ul>
</li>
<li>Upgrade <code>ruby-build</code>.
<ul>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3199">#3199</a> PR by <a href="https://github.com/ThisGuyCodes"><code>@​ThisGuyCodes</code></a>.</li>
</ul>
</li>
<li>Add &quot;sensible regex&quot; warnings to <code>repo: meta</code>.
<ul>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3311">#3311</a> PR by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
</ul>
</li>
<li>Add warnings for deprecated <code>stages</code> (<code>commit</code> -&gt; <code>pre-commit</code>, <code>push</code> -&gt;
<code>pre-push</code>, <code>merge-commit</code> -&gt; <code>pre-merge-commit</code>).
<ul>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3312">#3312</a> PR by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3313">#3313</a> PR by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3315">#3315</a> PR by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/2732">#2732</a> issue by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
</ul>
</li>
</ul>
<h3>Migrating</h3>
<ul>
<li><code>language: python_venv</code> has been removed -- use <code>language: python</code> instead.
<ul>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3320">#3320</a> PR by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/2734">#2734</a> issue by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md">pre-commit's changelog</a>.</em></p>
<blockquote>
<h1>4.0.0 - 2024-10-05</h1>
<h3>Features</h3>
<ul>
<li>Improve <code>pre-commit migrate-config</code> to handle more yaml formats.
<ul>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3301">#3301</a> PR by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
</ul>
</li>
<li>Handle <code>stages</code> deprecation in <code>pre-commit migrate-config</code>.
<ul>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3302">#3302</a> PR by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/2732">#2732</a> issue by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
</ul>
</li>
<li>Upgrade <code>ruby-build</code>.
<ul>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3199">#3199</a> PR by <a href="https://github.com/ThisGuyCodes"><code>@​ThisGuyCodes</code></a>.</li>
</ul>
</li>
<li>Add &quot;sensible regex&quot; warnings to <code>repo: meta</code>.
<ul>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3311">#3311</a> PR by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
</ul>
</li>
<li>Add warnings for deprecated <code>stages</code> (<code>commit</code> -&gt; <code>pre-commit</code>, <code>push</code> -&gt;
<code>pre-push</code>, <code>merge-commit</code> -&gt; <code>pre-merge-commit</code>).
<ul>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3312">#3312</a> PR by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3313">#3313</a> PR by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3315">#3315</a> PR by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/2732">#2732</a> issue by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
</ul>
</li>
</ul>
<h3>Migrating</h3>
<ul>
<li><code>language: python_venv</code> has been removed -- use <code>language: python</code> instead.
<ul>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3320">#3320</a> PR by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/2734">#2734</a> issue by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
</ul>
</li>
</ul>
<h1>3.8.0 - 2024-07-28</h1>
<h3>Features</h3>
<ul>
<li>Implement health checks for <code>language: r</code> so environments are recreated if
the system version of R changes.
<ul>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3206">#3206</a> issue by <a href="https://github.com/lorenzwalthert"><code>@​lorenzwalthert</code></a>.</li>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3265">#3265</a> PR by <a href="https://github.com/lorenzwalthert"><code>@​lorenzwalthert</code></a>.</li>
</ul>
</li>
</ul>
<h1>3.7.1 - 2024-05-10</h1>
<h3>Fixes</h3>
<ul>
<li>Fix <code>language: rust</code> default language version check when <code>rust-toolchain.toml</code>
is present.
<ul>
<li>issue by <a href="https://github.com/gaborbernat"><code>@​gaborbernat</code></a>.</li>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3201">#3201</a> PR by <a href="https://github.com/asottile"><code>@​asottile</code></a>.</li>
</ul>
</li>
</ul>
<h1>3.7.0 - 2024-03-24</h1>
<h3>Features</h3>
<ul>
<li>Use a tty for <code>docker</code> and <code>docker_image</code> hooks when <code>--color</code> is specified.
<ul>
<li><a href="https://redirect.github.com/pre-commit/pre-commit/issues/3122">#3122</a> PR by <a href="https://github.com/glehmann"><code>@​glehmann</code></a>.</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/pre-commit/pre-commit/commit/dbccd57db0e9cf993ea909e929eea97f6e4389ea"><code>dbccd57</code></a> v4.0.0</li>
<li><a href="https://github.com/pre-commit/pre-commit/commit/d07e52901ccd9ceecb0b8c17627b2803a974669b"><code>d07e529</code></a> Merge pull request <a href="https://redirect.github.com/pre-commit/pre-commit/issues/3320">#3320</a> from pre-commit/remove-python-venv</li>
<li><a href="https://github.com/pre-commit/pre-commit/commit/801b956304e2ad2738bdb76d9c65ed52e967bb57"><code>801b956</code></a> remove deprecated python_venv alias</li>
<li><a href="https://github.com/pre-commit/pre-commit/commit/a2f7b80e8952594985e2719d0d12cb194a5e855e"><code>a2f7b80</code></a> Merge pull request <a href="https://redirect.github.com/pre-commit/pre-commit/issues/3315">#3315</a> from pre-commit/warn-deprecated-stage-names-on-init</li>
<li><a href="https://github.com/pre-commit/pre-commit/commit/d31722386e57a98d8d7d6d74228d255b9a9ffaf3"><code>d317223</code></a> add warning for deprecates stages for remote repos on init</li>
<li><a href="https://github.com/pre-commit/pre-commit/commit/7555e11098696342f9d0b6dbd2e6d67fce14e301"><code>7555e11</code></a> Merge pull request <a href="https://redirect.github.com/pre-commit/pre-commit/issues/3314">#3314</a> from pre-commit/remove-log-info-mock</li>
<li><a href="https://github.com/pre-commit/pre-commit/commit/05e365fe08a63d8cb866a60cf9e68381b9e4284e"><code>05e365f</code></a> Merge pull request <a href="https://redirect.github.com/pre-commit/pre-commit/issues/3313">#3313</a> from pre-commit/default-stages-warning</li>
<li><a href="https://github.com/pre-commit/pre-commit/commit/1d2f1c0ccea63906c8bcc9265bb9940383341c0c"><code>1d2f1c0</code></a> replace log_info_mock with pytest's caplog</li>
<li><a href="https://github.com/pre-commit/pre-commit/commit/33e020f315a0f8654500ffbbb103ef7b39fd7ff9"><code>33e020f</code></a> add warning for deprecated stages values in <code>default_stages</code></li>
<li><a href="https://github.com/pre-commit/pre-commit/commit/e7cfc0d2cb33dbd1f98a4732d4d7498eca225e4e"><code>e7cfc0d</code></a> Merge pull request <a href="https://redirect.github.com/pre-commit/pre-commit/issues/3312">#3312</a> from pre-commit/warning-for-old-stage-names</li>
<li>Additional commits viewable in <a href="https://github.com/pre-commit/pre-commit/compare/v2.0.0...v4.0.0">compare view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 70e695a..911eb47 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,17 @@
 
 ## [Unreleased]
 
+### Added
+
+-   :sparkles: Implement missing PipelineML filtering functionalities to let kedro display resume hints and avoid breaking kedro-viz ([#377, Calychas](https://github.com/Galileo-Galilei/kedro-mlflow/pull/377), [#601, Calychas](https://github.com/Galileo-Galilei/kedro-mlflow/pull/601))
+-   :sparkles: Sanitize parameters name with unsupported characters to avoid mlflow errors when logging ([#595, pascalwhoop](https://github.com/Galileo-Galilei/kedro-mlflow/pull/595))
+
+## [0.13.2] - 2024-10-15
+
+### Fixed
+
+:bug: :ambulance: Fix `MlflowArtifactDataset` `load` and `save` methods to make them compatible with modern datasets without private `_load` and `_save` introduced in `kedro-datasets>=5.0.0` ([#598](https://github.com/Galileo-Galilei/kedro-mlflow/issues/598))
+
 ## [0.13.1] - 2024-09-24
 
 ### Added
@@ -502,7 +513,9 @@
 -   :sparkles: Add `MlflowDataSet` for artifacts autologging
 -   :sparkles: Add `PipelineMl` class and its `pipeline_ml` factory for pipeline packaging and service
 
-[Unreleased]: https://github.com/Galileo-Galilei/kedro-mlflow/compare/0.13.1...HEAD
+[Unreleased]: https://github.com/Galileo-Galilei/kedro-mlflow/compare/0.13.2...HEAD
+
+[0.13.2]: https://github.com/Galileo-Galilei/kedro-mlflow/compare/0.13.1...0.13.2
 
 [0.13.1]: https://github.com/Galileo-Galilei/kedro-mlflow/compare/0.13.0...0.13.1
 
diff --git a/README.md b/README.md
index a9b7ea7..7f98e9e 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
 ----------------------------------------------------------
 | Branch   | Tests                                                                                                                                                                                            | Coverage                                                                                                                                                         | Links                                                                                                                                                                                                           | Documentation                                                                                                                           | Deployment                                                                                                                                                                                                | Activity                                                                                                                                                              |
 | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `master` | [![test](https://github.com/Galileo-Galilei/kedro-mlflow/workflows/test/badge.svg?branch=master)](https://github.com/Galileo-Galilei/kedro-mlflow/actions?query=workflow%3Atest+branch%3Amaster) | [![codecov](https://codecov.io/gh/Galileo-Galilei/kedro-mlflow/branch/master/graph/badge.svg)](https://codecov.io/gh/Galileo-Galilei/kedro-mlflow/branch/master) | [![links](https://github.com/Galileo-Galilei/kedro-mlflow/workflows/check-links/badge.svg?branch=master)](https://github.com/Galileo-Galilei/kedro-mlflow/actions?query=workflow%3Acheck-links+branch%3Amaster) | [![Documentation](https://readthedocs.org/projects/kedro-mlflow/badge/?version=stable)](https://kedro-mlflow.readthedocs.io/en/stable/) | [![publish](https://github.com/Galileo-Galilei/kedro-mlflow/workflows/publish/badge.svg?branch=master)](https://github.com/Galileo-Galilei/kedro-mlflow/actions?query=branch%3Amaster+workflow%3Apublish) | [![commit](https://img.shields.io/github/commits-since/Galileo-Galilei/kedro-mlflow/0.13.1)](https://github.com/Galileo-Galilei/kedro-mlflow/compare/0.13.1...master) |
+| `master` | [![test](https://github.com/Galileo-Galilei/kedro-mlflow/workflows/test/badge.svg?branch=master)](https://github.com/Galileo-Galilei/kedro-mlflow/actions?query=workflow%3Atest+branch%3Amaster) | [![codecov](https://codecov.io/gh/Galileo-Galilei/kedro-mlflow/branch/master/graph/badge.svg)](https://codecov.io/gh/Galileo-Galilei/kedro-mlflow/branch/master) | [![links](https://github.com/Galileo-Galilei/kedro-mlflow/workflows/check-links/badge.svg?branch=master)](https://github.com/Galileo-Galilei/kedro-mlflow/actions?query=workflow%3Acheck-links+branch%3Amaster) | [![Documentation](https://readthedocs.org/projects/kedro-mlflow/badge/?version=stable)](https://kedro-mlflow.readthedocs.io/en/stable/) | [![publish](https://github.com/Galileo-Galilei/kedro-mlflow/workflows/publish/badge.svg?branch=master)](https://github.com/Galileo-Galilei/kedro-mlflow/actions?query=branch%3Amaster+workflow%3Apublish) | [![commit](https://img.shields.io/github/commits-since/Galileo-Galilei/kedro-mlflow/0.13.2)](https://github.com/Galileo-Galilei/kedro-mlflow/compare/0.13.2...master) |
 
 *If you like the repo, please give it a :star:*
 
diff --git a/docs/source/02_installation/01_installation.md b/docs/source/02_installation/01_installation.md
index 2a5cb3f..242abd8 100644
--- a/docs/source/02_installation/01_installation.md
+++ b/docs/source/02_installation/01_installation.md
@@ -77,10 +77,10 @@ projects. It is developed as part of
 the Kedro initiative at QuantumBlack.
 
 Installed plugins:
-kedro_mlflow: 0.13.1 (hooks:global,project)
+kedro_mlflow: 0.13.2 (hooks:global,project)
 ```
 
-The version ``0.13.1`` of the plugin is installed and has both global and project commands.
+The version ``0.13.2`` of the plugin is installed and has both global and project commands.
 
 That's it! You are now ready to go!
 
diff --git a/docs/source/03_getting_started/01_example_project.md b/docs/source/03_getting_started/01_example_project.md
index ff3fa39..a347e78 100644
--- a/docs/source/03_getting_started/01_example_project.md
+++ b/docs/source/03_getting_started/01_example_project.md
@@ -7,7 +7,7 @@ Create a conda environment and install ``kedro-mlflow`` (this will automatically
 ```console
 conda create -n km_example python=3.9 --yes
 conda activate km_example
-pip install kedro-mlflow==0.13.1
+pip install kedro-mlflow==0.13.2
 ```
 
 ## Install the toy project
diff --git a/kedro_mlflow/__init__.py b/kedro_mlflow/__init__.py
index e6868b1..3c849a2 100644
--- a/kedro_mlflow/__init__.py
+++ b/kedro_mlflow/__init__.py
@@ -1,6 +1,6 @@
 """kedro-mlflow plugin constants"""
 
-__version__ = "0.13.1"
+__version__ = "0.13.2"
 
 import logging
 
diff --git a/kedro_mlflow/framework/hooks/mlflow_hook.py b/kedro_mlflow/framework/hooks/mlflow_hook.py
index 41b4501..7aed8cc 100644
--- a/kedro_mlflow/framework/hooks/mlflow_hook.py
+++ b/kedro_mlflow/framework/hooks/mlflow_hook.py
@@ -1,3 +1,5 @@
+import os
+import re
 from logging import Logger, getLogger
 from pathlib import Path
 from tempfile import TemporaryDirectory
@@ -303,6 +305,11 @@ class MlflowHook:
                     d=params_inputs, recursive=self.recursive, sep=self.sep
                 )
 
+            # sanitize params inputs to avoid mlflow errors
+            params_inputs = {
+                self.sanitize_param_name(k): v for k, v in params_inputs.items()
+            }
+
             # logging parameters based on defined strategy
             for k, v in params_inputs.items():
                 self._log_param(k, v)
@@ -446,5 +453,26 @@ class MlflowHook:
             # hence it should not be modified. this is only a safeguard
             switch_catalog_logging(catalog, True)
 
+    def sanitize_param_name(self, name: str) -> str:
+        # regex taken from MLFlow codebase: https://github.com/mlflow/mlflow/blob/e40e782b6fcab473159e6d4fee85bc0fc10f78fd/mlflow/utils/validation.py#L140C1-L148C44
+
+        # for windows colon ':' are not accepted
+        matching_pattern = r"^[/\w.\- ]*$" if is_windows() else r"^[/\w.\- :]*$"
+
+        if re.match(matching_pattern, name):
+            return name
+        else:
+            replacement_pattern = r"[^/\w.\- ]" if is_windows() else r"[^/\w.\- :]"
+            # Replace invalid characters with underscore
+            sanitized_name = re.sub(replacement_pattern, "_", name)
+            self._logger.warning(
+                f"'{name}' is not a valid name for a mlflow paramter. It is renamed as '{sanitized_name}'"
+            )
+            return sanitized_name
+
+
+def is_windows():
+    return os.name == "nt"
+
 
 mlflow_hook = MlflowHook()
diff --git a/kedro_mlflow/io/artifacts/mlflow_artifact_dataset.py b/kedro_mlflow/io/artifacts/mlflow_artifact_dataset.py
index 64ced10..9e1d42f 100644
--- a/kedro_mlflow/io/artifacts/mlflow_artifact_dataset.py
+++ b/kedro_mlflow/io/artifacts/mlflow_artifact_dataset.py
@@ -62,7 +62,7 @@ class MlflowArtifactDataset(AbstractVersionedDataset):
                 # for logging on remote storage like Azure S3
                 local_path = local_path.as_posix()
 
-                if getattr(super().save, "__savewrapped__", False):  # modern dataset
+                if hasattr(super().save, "__wrapped__"):  # modern dataset
                     super().save.__wrapped__(self, data)
                 else:  # legacy dataset
                     super()._save(data)
@@ -134,7 +134,7 @@ class MlflowArtifactDataset(AbstractVersionedDataset):
                     shutil.copy(src=temp_download_filepath, dst=local_path)
 
                 # finally, read locally
-                if getattr(super().load, "__loadwrapped__", False):  # modern dataset
+                if hasattr(super().load, "__wrapped__"):  # modern dataset
                     return super().load.__wrapped__(self)
                 else:  # legacy dataset
                     return super()._load()
diff --git a/kedro_mlflow/pipeline/pipeline_ml.py b/kedro_mlflow/pipeline/pipeline_ml.py
index bdd6f80..5283190 100644
--- a/kedro_mlflow/pipeline/pipeline_ml.py
+++ b/kedro_mlflow/pipeline/pipeline_ml.py
@@ -4,17 +4,10 @@ from typing import Dict, Iterable, Optional, Union
 from kedro.pipeline import Pipeline
 from kedro.pipeline.node import Node
 
-MSG_NOT_IMPLEMENTED = (
-    "This method is not implemented because it does "
-    "not make sense for 'PipelineML'. "
-    "Manipulate directly the training pipeline and "
-    "recreate the 'PipelineML' with 'pipeline_ml_factory' factory."
-)
-
-MSG_WARNING_KEDRO_VIZ = (
+MSG_WARNING_PIPELINEML_DEMOTED = (
     "BEWARE - This 'Pipeline' is no longer a 'PipelineML' object. "
-    "This method is only implemented for compatibility with kedro-viz "
-    "but should never be used directly.\nSee "
+    "This method is only implemented for compatibility with kedro-viz and pipeline resume hints on failure."
+    "It should never be used directly.\nSee "
     "https://github.com/Galileo-Galilei/kedro-mlflow/issues/569 "
     " for more context. "
 )
@@ -173,16 +166,18 @@ class PipelineML(Pipeline):
         )
 
     def only_nodes(self, *node_names: str) -> "Pipeline":  # pragma: no cover
-        raise NotImplementedError(MSG_NOT_IMPLEMENTED)
+        self._logger.warning(MSG_WARNING_PIPELINEML_DEMOTED)
+        return self.training.only_nodes(*node_names)
 
     def only_nodes_with_namespace(
         self, node_namespace: str
     ) -> "Pipeline":  # pragma: no cover
-        self._logger.warning(MSG_WARNING_KEDRO_VIZ)
+        self._logger.warning(MSG_WARNING_PIPELINEML_DEMOTED)
         return self.training.only_nodes_with_namespace(node_namespace)
 
-    def only_nodes_with_inputs(self, *inputs: str) -> "PipelineML":  # pragma: no cover
-        raise NotImplementedError(MSG_NOT_IMPLEMENTED)
+    def only_nodes_with_inputs(self, *inputs: str) -> "Pipeline":  # pragma: no cover
+        self._logger.warning(MSG_WARNING_PIPELINEML_DEMOTED)
+        return self.training.only_nodes_with_inputs(*inputs)
 
     def from_inputs(self, *inputs: str) -> "PipelineML":  # pragma: no cover
         # exceptionnally, we don't call super() because it raises
@@ -194,10 +189,9 @@ class PipelineML(Pipeline):
         pipeline = self.training.from_inputs(*inputs)
         return self._turn_pipeline_to_ml(pipeline)
 
-    def only_nodes_with_outputs(
-        self, *outputs: str
-    ) -> "PipelineML":  # pragma: no cover
-        raise NotImplementedError(MSG_NOT_IMPLEMENTED)
+    def only_nodes_with_outputs(self, *outputs: str) -> "Pipeline":  # pragma: no cover
+        self._logger.warning(MSG_WARNING_PIPELINEML_DEMOTED)
+        return self.training.only_nodes_with_outputs(*outputs)
 
     def to_outputs(self, *outputs: str) -> "PipelineML":  # pragma: no cover
         # see from_inputs for an explanation of why we don't call super()
@@ -225,13 +219,13 @@ class PipelineML(Pipeline):
 
     def filter(
         self,
-        tags: Iterable[str] = None,
-        from_nodes: Iterable[str] = None,
-        to_nodes: Iterable[str] = None,
-        node_names: Iterable[str] = None,
-        from_inputs: Iterable[str] = None,
-        to_outputs: Iterable[str] = None,
-        node_namespace: str = None,
+        tags: Optional[Iterable[str]] = None,
+        from_nodes: Optional[Iterable[str]] = None,
+        to_nodes: Optional[Iterable[str]] = None,
+        node_names: Optional[Iterable[str]] = None,
+        from_inputs: Optional[Iterable[str]] = None,
+        to_outputs: Optional[Iterable[str]] = None,
+        node_namespace: Optional[str] = None,
     ) -> "Pipeline":
         # see from_inputs for an explanation of why we don't call super()
         pipeline = self.training.filter(
@@ -246,10 +240,11 @@ class PipelineML(Pipeline):
         return self._turn_pipeline_to_ml(pipeline)
 
     def __add__(self, other):  # pragma: no cover
-        raise NotImplementedError(MSG_NOT_IMPLEMENTED)
+        self._logger.warning(MSG_WARNING_PIPELINEML_DEMOTED)
+        return self.training + other
 
     def __sub__(self, other):  # pragma: no cover
-        self._logger.warning(MSG_WARNING_KEDRO_VIZ)
+        self._logger.warning(MSG_WARNING_PIPELINEML_DEMOTED)
         return self.training - other
 
     def __and__(self, other):  # pragma: no cover
@@ -258,7 +253,8 @@ class PipelineML(Pipeline):
         return self._turn_pipeline_to_ml(new_pipeline)
 
     def __or__(self, other):  # pragma: no cover
-        raise NotImplementedError(MSG_NOT_IMPLEMENTED)
+        self._logger.warning(MSG_WARNING_PIPELINEML_DEMOTED)
+        return self.training | other
 
 
 class KedroMlflowPipelineMLError(Exception):
diff --git a/pyproject.toml b/pyproject.toml
index 77d87c8..be7ebd8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -82,7 +82,7 @@ docstring-code-format = false
 docstring-code-line-length = "dynamic"
 
 [tool.bumpversion]
-current_version = "0.13.1"
+current_version = "0.13.2"
 
 [[tool.bumpversion.files]]
 filename = "setup.py"
diff --git a/setup.py b/setup.py
index 6482a6c..7ef82b8 100644
--- a/setup.py
+++ b/setup.py
@@ -25,7 +25,7 @@ with open((HERE / "README.md").as_posix(), encoding="utf-8") as file_handler:
 
 setup(
     name=NAME,
-    version="0.13.1",  # this will be bumped automatically by bump-my-version
+    version="0.13.2",  # this will be bumped automatically by bump-my-version
     description="A kedro-plugin to use mlflow in your kedro projects",
     license="Apache Software License (Apache 2.0)",
     long_description=README,
@@ -55,7 +55,7 @@ setup(
             "kedro-datasets[pandas.CSVDataSet]",
         ],
         "dev": [
-            "pre-commit>=2.0.0,<4.0.0",
+            "pre-commit>=2.0.0,<5.0.0",
             "jupyter>=1.0.0,<2.0.0",
         ],
     },


Test patch:
diff --git a/tests/framework/hooks/test_hook_log_parameters.py b/tests/framework/hooks/test_hook_log_parameters.py
index 4bbd5d9..286858b 100644
--- a/tests/framework/hooks/test_hook_log_parameters.py
+++ b/tests/framework/hooks/test_hook_log_parameters.py
@@ -1,3 +1,4 @@
+import os
 from pathlib import Path
 from typing import Dict
 
@@ -74,6 +75,136 @@ def dummy_catalog():
     return catalog
 
 
+@pytest.mark.parametrize(
+    "param_name,expected_name",
+    [
+        ("valid_param", "valid_param"),
+        ("valid-param", "valid-param"),
+        ("invalid/param", "invalid/param"),
+        ("invalid.param", "invalid.param"),
+        ("[invalid]$param", "_invalid__param"),
+    ],
+)
+def test_parameter_name_sanitization(
+    kedro_project, dummy_run_params, param_name, expected_name
+):
+    mlflow_tracking_uri = (kedro_project / "mlruns").as_uri()
+    mlflow.set_tracking_uri(mlflow_tracking_uri)
+
+    node_inputs = {f"params:{param_name}": "test_value"}
+
+    bootstrap_project(kedro_project)
+    with KedroSession.create(
+        project_path=kedro_project,
+    ) as session:
+        context = session.load_context()
+        mlflow_node_hook = MlflowHook()
+        mlflow_node_hook.after_context_created(context)
+
+        with mlflow.start_run():
+            mlflow_node_hook.before_pipeline_run(
+                run_params=dummy_run_params,
+                pipeline=Pipeline([]),
+                catalog=DataCatalog(),
+            )
+            mlflow_node_hook.before_node_run(
+                node=node(func=lambda x: x, inputs=dict(x="a"), outputs=None),
+                catalog=DataCatalog(),
+                inputs=node_inputs,
+                is_async=False,
+            )
+            run_id = mlflow.active_run().info.run_id
+
+        mlflow_client = MlflowClient(mlflow_tracking_uri)
+        current_run = mlflow_client.get_run(run_id)
+        assert expected_name in current_run.data.params
+        assert current_run.data.params[expected_name] == "test_value"
+
+
+@pytest.mark.skipif(
+    os.name != "nt", reason="Windows does not log params with colon symbol"
+)
+def test_parameter_name_with_colon_sanitization_on_windows(
+    kedro_project, dummy_run_params
+):
+    param_name = "valid:param"
+    expected_name = "valid_param"
+
+    mlflow_tracking_uri = (kedro_project / "mlruns").as_uri()
+    mlflow.set_tracking_uri(mlflow_tracking_uri)
+
+    node_inputs = {f"params:{param_name}": "test_value"}
+
+    bootstrap_project(kedro_project)
+    with KedroSession.create(
+        project_path=kedro_project,
+    ) as session:
+        context = session.load_context()
+        mlflow_node_hook = MlflowHook()
+        mlflow_node_hook.after_context_created(context)
+
+        with mlflow.start_run():
+            mlflow_node_hook.before_pipeline_run(
+                run_params=dummy_run_params,
+                pipeline=Pipeline([]),
+                catalog=DataCatalog(),
+            )
+            mlflow_node_hook.before_node_run(
+                node=node(func=lambda x: x, inputs=dict(x="a"), outputs=None),
+                catalog=DataCatalog(),
+                inputs=node_inputs,
+                is_async=False,
+            )
+            run_id = mlflow.active_run().info.run_id
+
+        mlflow_client = MlflowClient(mlflow_tracking_uri)
+        current_run = mlflow_client.get_run(run_id)
+        assert expected_name in current_run.data.params
+        assert current_run.data.params[expected_name] == "test_value"
+
+
+@pytest.mark.skipif(
+    os.name == "nt", reason="Linux and Mac do log params with colon symbol"
+)
+def test_parameter_name_with_colon_sanitization_on_mac_linux(
+    kedro_project, dummy_run_params
+):
+    param_name = "valid:param"
+    expected_name = "valid:param"
+
+    mlflow_tracking_uri = (kedro_project / "mlruns").as_uri()
+    mlflow.set_tracking_uri(mlflow_tracking_uri)
+
+    node_inputs = {f"params:{param_name}": "test_value"}
+
+    bootstrap_project(kedro_project)
+    with KedroSession.create(
+        project_path=kedro_project,
+    ) as session:
+        context = session.load_context()
+        mlflow_node_hook = MlflowHook()
+        mlflow_node_hook.after_context_created(context)
+
+        with mlflow.start_run():
+            mlflow_node_hook.before_pipeline_run(
+                run_params=dummy_run_params,
+                pipeline=Pipeline([]),
+                catalog=DataCatalog(),
+            )
+            mlflow_node_hook.before_node_run(
+                node=node(func=lambda x: x, inputs=dict(x="a"), outputs=None),
+                catalog=DataCatalog(),
+                inputs=node_inputs,
+                is_async=False,
+            )
+            run_id = mlflow.active_run().info.run_id
+
+        mlflow_client = MlflowClient(mlflow_tracking_uri)
+        current_run = mlflow_client.get_run(run_id)
+        assert expected_name in current_run.data.params
+        assert current_run.data.params[expected_name] == "test_value"
+
+
 def test_pipeline_run_hook_getting_configs(
     kedro_project,
     dummy_run_params,
diff --git a/tests/pipeline/test_pipeline_ml.py b/tests/pipeline/test_pipeline_ml.py
index a56f8ff..63d3763 100644
--- a/tests/pipeline/test_pipeline_ml.py
+++ b/tests/pipeline/test_pipeline_ml.py
@@ -90,6 +90,7 @@ def pipeline_ml_with_intermediary_artifacts():
                 inputs="data",
                 outputs="encoder",
                 tags=["training"],
+                name="node_fit_encoder_fun_data",
             ),
             node(
                 func=apply_encoder_fun,
@@ -258,11 +259,69 @@ def catalog_with_parameters():
     return catalog_with_parameters
 
 
+def test_pipeline_ml_only_nodes(
+    caplog,
+    pipeline_ml_with_intermediary_artifacts,
+):
+    """When the pipeline is filtered with only_nodes, we return only the training pipeline. This is for kedro-viz and resume hints compatibility"""
+
+    # pipeline_ml_with_namespace are fixture in conftest
+
+    # remember : the arguments are iterable, so do not pass string directly (e.g ["training"] rather than training)
+
+    filtered_pipeline_ml = pipeline_ml_with_intermediary_artifacts.only_nodes(
+        "node_fit_encoder_fun_data"
+    )
+
+    # PipelineML class must be preserved when filtering
+    # inference should be unmodified
+    # training pipeline nodes must be identical to kedro filtering.
+    assert isinstance(filtered_pipeline_ml, Pipeline)
+    assert not isinstance(filtered_pipeline_ml, PipelineML)
+    assert str(filtered_pipeline_ml) == str(
+        pipeline_ml_with_intermediary_artifacts.training.only_nodes(
+            "node_fit_encoder_fun_data"
+        )
+    )
+    assert (
+        "for compatibility with kedro-viz and pipeline resume hints on failure"
+        in caplog.text
+    )
+
+
+def test_pipeline_ml_only_nodes_with_outputs(
+    caplog,
+    pipeline_ml_with_intermediary_artifacts,
+):
+    """When the pipeline is filtered with only_nodes, we return only the training pipeline. This is for kedro-viz and resume hints compatibility"""
+
+    # pipeline_ml_with_intermediary_artifacts are fixture in conftest
+
+    # remember : the arguments are iterable, so do not pass string directly (e.g ["training"] rather than training)
+
+    filtered_pipeline_ml = (
+        pipeline_ml_with_intermediary_artifacts.only_nodes_with_outputs("data")
+    )
+
+    # PipelineML class must be preserved when filtering
+    # inference should be unmodified
+    # training pipeline nodes must be identical to kedro filtering.
+    assert isinstance(filtered_pipeline_ml, Pipeline)
+    assert not isinstance(filtered_pipeline_ml, PipelineML)
+    assert str(filtered_pipeline_ml) == str(
+        pipeline_ml_with_intermediary_artifacts.training.only_nodes_with_outputs("data")
+    )
+    assert (
+        "for compatibility with kedro-viz and pipeline resume hints on failure"
+        in caplog.text
+    )
+
+
 def test_pipeline_ml_only_nodes_with_namespace(
     caplog,
     pipeline_ml_with_namespace,
 ):
-    """When the pipeline is filtered with only_nodes_with_namespace, we return only the training pipeline. This is for kedro viz compatibility"""
+    """When the pipeline is filtered with only_nodes, we return only the training pipeline. This is for kedro-viz and resume hints compatibility"""
 
     # pipeline_ml_with_namespace are fixture in conftest
 
@@ -278,7 +337,10 @@ def test_pipeline_ml_only_nodes_with_namespace(
     assert isinstance(filtered_pipeline_ml, Pipeline)
     assert not isinstance(filtered_pipeline_ml, PipelineML)
     assert str(filtered_pipeline_ml) == str(pipeline_ml_with_namespace.training)
-    assert "kedro-viz but should never be" in caplog.text
+    assert (
+        "for compatibility with kedro-viz and pipeline resume hints on failure"
+        in caplog.text
+    )
 
 
 def test_pipeline_ml_substraction(
@@ -301,7 +363,58 @@ def test_pipeline_ml_substraction(
     # training pipeline nodes must be identical to kedro filtering.
     assert isinstance(filtered_pipeline_ml, Pipeline)
     assert not isinstance(filtered_pipeline_ml, PipelineML)
-    assert "kedro-viz but should never be" in caplog.text
+    assert (
+        "for compatibility with kedro-viz and pipeline resume hints on failure"
+        in caplog.text
+    )
+
+
+def test_pipeline_ml_addition(
+    caplog,
+    pipeline_ml_with_namespace,
+    pipeline_ml_with_tag,
+):
+    """When the pipeline is filtered with only_nodes_with_namespace, we return only the training pipeline. This is for kedro viz compatibility"""
+
+    # pipeline_ml_with_namespace are fixture in conftest
+
+    # remember : the arguments are iterable, so do not pass string directly (e.g ["training"] rather than training)
+
+    sum_of_pipeline_ml = pipeline_ml_with_namespace + pipeline_ml_with_tag
+
+    # PipelineML class must be preserved when filtering
+    # inference should be unmodified
+    # training pipeline nodes must be identical to kedro filtering.
+    assert isinstance(sum_of_pipeline_ml, Pipeline)
+    assert not isinstance(sum_of_pipeline_ml, PipelineML)
+    assert (
+        "for compatibility with kedro-viz and pipeline resume hints on failure"
+        in caplog.text
+    )
+
+
+def test_pipeline_ml_or(
+    caplog,
+    pipeline_ml_with_namespace,
+    pipeline_ml_with_tag,
+):
+    """When the pipeline is filtered with only_nodes_with_namespace, we return only the training pipeline. This is for kedro viz compatibility"""
+
+    # pipeline_ml_with_namespace are fixture in conftest
+
+    # remember : the arguments are iterable, so do not pass string directly (e.g ["training"] rather than training)
+
+    or_of_pipeline_ml = pipeline_ml_with_namespace | pipeline_ml_with_tag
+
+    # PipelineML class must be preserved when filtering
+    # inference should be unmodified
+    # training pipeline nodes must be identical to kedro filtering.
+    assert isinstance(or_of_pipeline_ml, Pipeline)
+    assert not isinstance(or_of_pipeline_ml, PipelineML)
+    assert (
+        "for compatibility with kedro-viz and pipeline resume hints on failure"
+        in caplog.text
+    )
 
 
 @pytest.mark.parametrize(
@@ -316,7 +429,7 @@ def test_pipeline_ml_substraction(
         (None, None, None, None, ["data"]),
     ],
 )
-def test_filtering_pipeline_ml(
+def test_pipeline_ml_filtering(
     mocker,
     pipeline_with_tag,
     pipeline_ml_with_tag,
@@ -374,7 +487,7 @@ def test_filtering_pipeline_ml(
         (None, None, None, ["preprocess_fun([raw_data]) -> [data]"], None),
     ],
 )
-def test_filtering_generate_invalid_pipeline_ml(
+def test_pipeline_ml__filtering_generate_invalid_pipeline_ml(
     mocker,
     pipeline_ml_obj,
     tags,
@@ -405,7 +518,7 @@ def test_filtering_generate_invalid_pipeline_ml(
 #     pass
 
 
-def test_too_many_free_inputs():
+def test_pipeline_ml_too_many_free_inputs():
     with pytest.raises(KedroMlflowPipelineMLError, match="No free input is allowed"):
         pipeline_ml_factory(
             training=Pipeline(
@@ -430,7 +543,7 @@ def test_too_many_free_inputs():
         )
 
 
-def test_tagging(pipeline_ml_with_tag):
+def test_pipeline_ml_tagging(pipeline_ml_with_tag):
     new_pl = pipeline_ml_with_tag.tag(["hello"])
     assert all(["hello" in node.tags for node in new_pl.nodes])