Repository: Galileo-Galilei/kedro-mlflow (language: python)

Issue:
Release 0.5.0
Bump version and CHANGELOG for next release.
Documentation improvements
A catch-all ticket to gather documentation improvement ideas.

1. Mark code blocks in README.md's Markdown with specific language - you can use 
```
```python
``` 
or
```
```yml
```
at the beginning of a code block to enable syntax highlighting.


Discussion / hints:

Hello @kaemo, this is the next task I will be working on. I have in mind the following items (in addition to your remark): 
- [x] Add community files to enforce collaboration (I will likely stick to github defaults here): 
    - [x] Code of conduct
    - [x] Contributing guidelines
    - [x] Issue templates
    - [x] Pull request template
- [x] Define a detailed overview of the documentation structure.
    - [x] Introduction
        - [X] introduction.md : Description of kedro and mlflow, and brief comparison
        - [x] motivation.md : justification for the need of a plugin to fill a gap 

- [x] Correct the README which is obsolete since ``kedro`` release ``0.16``.
Update the readme:
- [x] Add a section to warn for the beta status of the plugin (and specify we use SemVer)
- [x] Add a section with comptibiltiy between mlflow, kedro and kedro mlflow different versions
- [x] Move "main contributors" section to the end of the README with the contributing section

Fix patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
deleted file mode 100644
index b080da72..00000000
--- a/.bumpversion.cfg
+++ /dev/null
@@ -1,18 +0,0 @@
-[bumpversion]
-current_version = 0.4.1
-
-[bumpversion:file:setup.py]
-
-[bumpversion:file:kedro_mlflow/__init__.py]
-
-[bumpversion:file:README.md]
-
-[bumpversion:file:docs/source/01_introduction/02_motivation.md]
-
-[bumpversion:file:docs/source/02_installation/01_installation.md]
-
-[bumpversion:file:docs/source/03_getting_started/01_example_project.md]
-
-[bumpversion:file:docs/source/04_experimentation_tracking/02_version_parameters.md]
-
-[bumpversion:file:docs/source/04_experimentation_tracking/03_version_datasets.md]
diff --git a/.flake8 b/.flake8
deleted file mode 100644
index ac31e298..00000000
--- a/.flake8
+++ /dev/null
@@ -1,6 +0,0 @@
-[flake8]
-ignore = E203, E266, E501, W503
-max-line-length = 88
-max-complexity = 18
-select = B,C,E,F,W,T4,B9
-per-file-ignores = **/__init__.py:F401
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..491deae0
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,7 @@
+version: 2
+updates:
+- package-ecosystem: pip
+  directory: "/"
+  schedule:
+    interval: daily
+  open-pull-requests-limit: 10
diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml
index 51e4d44a..f91130ce 100644
--- a/.github/workflows/prepare-release.yml
+++ b/.github/workflows/prepare-release.yml
@@ -36,19 +36,23 @@ jobs:
       uses: thomaseizinger/keep-a-changelog-new-release@v1
       with:
         version: ${{ steps.bump_version.outputs.package_version }}
-    - name: Commit changes to a release-candidate branch
+    - name: Create a new release branch
       run: |
         git config user.name github-actions
         git config user.email github-actions@github.com
-        git checkout -b release-candidate-${{ steps.bump_version.outputs.package_version }}
-        git commit -am "Release ${{ steps.bump_version.outputs.package_version }} - Bump version and CHANGELOG"
-        git push -u origin release-candidate-${{ steps.bump_version.outputs.package_version }}
-    - name: Open a PR to merge the release candidate to master
+        git checkout -b release-${{ steps.bump_version.outputs.package_version }}
+        git push -u origin release-${{ steps.bump_version.outputs.package_version }}
+    - name: Open a PR to merge the release to master
+      id: open_pr
       uses: vsoch/pull-request-action@1.0.12
       env:
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         PULL_REQUEST_BRANCH: master
-        PULL_REQUEST_FROM_BRANCH: release-candidate-${{ steps.bump_version.outputs.package_version }}
-        PULL_REQUEST_TITLE: "Release candidate ${{ steps.bump_version.outputs.package_version }}"
+        PULL_REQUEST_FROM_BRANCH: release-${{ steps.bump_version.outputs.package_version }}
+        PULL_REQUEST_TITLE: "Release ${{ steps.bump_version.outputs.package_version }}"
         PULL_REQUEST_BODY: "Bump version and CHANGELOG for next release."
-        # PULL_REQUEST_ASSIGNEES: "${{ github.repository_owner }}"  # currently buggued in the action
+        PULL_REQUEST_ASSIGNEES: "${{ github.repository_owner }}"
+    - name: Commit the changes
+      run: |
+        git commit -am "FIX #${{ steps.open_pr.outputs.pull_request_number }}  - Bump version and CHANGELOG for release ${{ steps.bump_version.outputs.package_version }}"
+        git push
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 5003d116..a4b6e700 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -32,21 +32,22 @@ jobs:
         git checkout -b develop --track origin/develop
         git merge master
         git push
-    - name: Set dynamically package version as environment variable
+    - name: Set dynamically package version as output variable # see https://github.com/actions/create-release/issues/39
       # see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable
+      id: set_package_version
       run: |
-        echo "::set-env name=PACKAGE_VERSION::$(cat $PYTHON_PACKAGE/__init__.py | grep -Po  '\d+\.\d+\.\d+')"
+        echo "::set-output name=PACKAGE_VERSION::$(cat $PYTHON_PACKAGE/__init__.py | grep -Po  '\d+\.\d+\.\d+')"
     - name: Create temporary file with the body content for the release
       run: |
-        grep -Poz "## \[${{env.PACKAGE_VERSION}}] - \d{4}-\d{2}-\d{2}[\S\s]+?(?=## \[\d+\.\d+\.\d+\]|\[.+\]:)" CHANGELOG.md > release_body.md
+        grep -Poz "## \[${{steps.set_package_version.outputs.PACKAGE_VERSION}}] - \d{4}-\d{2}-\d{2}[\S\s]+?(?=## \[\d+\.\d+\.\d+\]|\[.+\]:)" CHANGELOG.md > release_body.md
     - name: Create Release # https://github.com/actions/create-release
       id: create_release
       uses: actions/create-release@v1.1.4
       env:
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
       with:
-        tag_name: ${{ env.PACKAGE_VERSION }}
-        release_name: Release ${{ env.PACKAGE_VERSION }}
+        tag_name: ${{ steps.set_package_version.outputs.PACKAGE_VERSION }}
+        release_name: Release ${{ steps.set_package_version.outputs.PACKAGE_VERSION }}
         body_path: ./release_body.md
         draft: false
         prerelease: false
diff --git a/.isort.cfg b/.isort.cfg
deleted file mode 100644
index be2b4cb2..00000000
--- a/.isort.cfg
+++ /dev/null
@@ -1,10 +0,0 @@
-[settings]
-multi_line_output=3
-include_trailing_comma=True
-force_grid_wrap=0
-use_parentheses=True
-line_length=88
-ensure_newline_before_comments=True
-sections=FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
-known_first_party=kedro_mlflow
-known_third_party=anyconfig,black,click,cookiecutter,flake8,isort,jinja2,kedro,mlflow,pandas,pytest,pytest_lazyfixture,setuptools,sklearn,yaml
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 4948a041..e9b38d26 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -18,6 +18,7 @@ formats:
 python:
   version: 3.6
   install:
-    - requirements: requirements/docs_requirements.txt
-    - method: setuptools
+    - method: pip
       path: .
+      extra_requirements:
+       - docs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8cacffa3..1cc25ecd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,25 @@
 
 ## [Unreleased]
 
+## [0.5.0] - 2021-02-21
+
+### Added
+
+-   A new `long_parameters_strategy` key is added in the `mlflow.yml` (under in the hook/node section). You can specify different strategies (`fail`, `truncate` or `tag`) to handle parameters over 250 characters which cause crashes for some mlflow backend. ([#69](https://github.com/Galileo-Galilei/kedro-mlflow/issues/69))
+-   Add an `env` parameter to `kedro mlflow init` command to specify under which `conf/` subfolder the `mlflow.yml` should be created. ([#159](https://github.com/Galileo-Galilei/kedro-mlflow/issues/159))
+-   The input parameters of the `inference` pipeline of a `PipelineML` object are now automatically pickle-ised and converted as artifacts. ([#158](https://github.com/Galileo-Galilei/kedro-mlflow/issues/158))
+-   [Detailed documentation on how to use `pipeline_ml_factory`](https://kedro-mlflow.readthedocs.io/en/latest/source/05_framework_ml/index.html) function, and more generally on how to use `kedro-mlflow` as mlops framework. This comes from [an example repo `kedro-mlflow-tutorial`](https://github.com/Galileo-Galilei/kedro-mlflow-tutorial). ([#16](https://github.com/Galileo-Galilei/kedro-mlflow/issues/16))
+
+### Fixed
+
+-   Pin the kedro version to force it to be **strictly** inferior to `0.17` which is not compatible with current `kedro-mlflow` version ([#143](https://github.com/Galileo-Galilei/kedro-mlflow/issues/143))
+-   It is no longer assumed for the project to run that the `mlflow.yml` is located under `conf/base`. The project will run as soon as the configuration file is discovered by the registered ConfigLoader ([#159](https://github.com/Galileo-Galilei/kedro-mlflow/issues/159))
+
+### Changed
+
+-   The `KedroPipelineModel.load_context()` method now loads all the `DataSets` in memory in the `DataCatalog`. It is also now possible to specify the `runner` to execute the model as well as the `copy_mode` when executing the inference pipeline (instead of deepcopying the datasets between each nodes which is kedro's default). This makes the API serving with `mlflow serve` command considerably faster (~20 times faster) for models which need compiling (e.g. keras, tensorflow ...) ([#133](https://github.com/Galileo-Galilei/kedro-mlflow/issues/133))
+-   The CLI projects commands are now always accessible even if you have not called `kedro mlflow init` yet to create a `mlflow.yml` configuration file ([#159](https://github.com/Galileo-Galilei/kedro-mlflow/issues/159))
+
 ## [0.4.1] - 2020-12-03
 
 ### Added
@@ -29,7 +48,7 @@
 -   `pipeline_ml_factory` now accepts that `inference` pipeline `inputs` may be in `training` pipeline `inputs` ([#71](https://github.com/Galileo-Galilei/kedro-mlflow/issues/71))
 -   `pipeline_ml_factory` now infer automatically the schema of the input dataset to validate data automatically at inference time. The output schema can be declared manually in `model_signature` argument ([#70](https://github.com/Galileo-Galilei/kedro-mlflow/issues/70))
 -   Add two DataSets for model logging and saving: `MlflowModelLoggerDataSet` and `MlflowModelSaverDataSet` ([#12](https://github.com/Galileo-Galilei/kedro-mlflow/issues/12))
--   `MlflowPipelineHook` and `MlflowNodeHook` are now [auto-registered](https://kedro.readthedocs.io/en/latest/07_extend_kedro/04_hooks.html#registering-your-hook-implementations-with-kedro) if you use `kedro>=0.16.4` ([#29](https://github.com/Galileo-Galilei/kedro-mlflow/issues/29))
+-   `MlflowPipelineHook` and `MlflowNodeHook` are now [auto-registered](https://kedro.readthedocs.io/en/latest/07_extend_kedro/02_hooks.html#registering-your-hook-implementations-with-kedro) if you use `kedro>=0.16.4` ([#29](https://github.com/Galileo-Galilei/kedro-mlflow/issues/29))
 
 ### Fixed
 
@@ -49,7 +68,7 @@
 
 ### Removed
 
--   `kedro mlflow init` command is no longer declaring hooks in `run.py`. You must now [register your hooks manually](docs/source/03_tutorial/02_setup.md#declaring-kedro-mlflow-hooks) in the `run.py` if you use `kedro>=0.16.0, <0.16.3` ([#62](https://github.com/Galileo-Galilei/kedro-mlflow/issues/62)).
+-   `kedro mlflow init` command is no longer declaring hooks in `run.py`. You must now [register your hooks manually](https://kedro-mlflow.readthedocs.io/en/stable/source/02_installation/02_setup.html#declaring-kedro-mlflow-hooks) in the `run.py` if you use `kedro>=0.16.0, <0.16.3` ([#62](https://github.com/Galileo-Galilei/kedro-mlflow/issues/62)).
 -   Remove `pipeline_ml` function which was deprecated in 0.3.0. It is now replaced by `pipeline_ml_factory` ([#105](https://github.com/Galileo-Galilei/kedro-mlflow/issues/105))
 -   Remove `MlflowDataSet` dataset which was deprecated in 0.3.0. It is now replaced by `MlflowArtifactDataSet` ([#105](https://github.com/Galileo-Galilei/kedro-mlflow/issues/105))
 
@@ -152,7 +171,9 @@ Many documentation improvements:
 
 [0.1.0]: https://github.com/Galileo-Galilei/kedro-mlflow/releases/tag/0.1.0
 
-[Unreleased]: https://github.com/Galileo-Galilei/kedro-mlflow/compare/0.4.1...HEAD
+[Unreleased]: https://github.com/Galileo-Galilei/kedro-mlflow/compare/0.5.0...HEAD
+
+[0.5.0]: https://github.com/Galileo-Galilei/kedro-mlflow/compare/0.4.1...0.5.0
 
 [0.4.1]: https://github.com/Galileo-Galilei/kedro-mlflow/compare/0.4.0...0.4.1
 
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4caea64a..e81814ae 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,11 +2,14 @@
 
 The current workflow is the following:
 
-1. Open an issue to describe your feature request or your bug fix with detailed explanation of what you want to achieve.
+1. Open an issue to describe your feature request or your bug fix with a detailed explanation of what you want to achieve.
 2. Fork the repo
 3. Develop locally:
     - Install the precommit file (`pip install pre-commit`, then `pre-commit install`)
     - Create a branch based on the develop branch (``git checkout -b <prefix-branchname> develop``)
+    - Create a conda environment (conda create -n <your-env-name> python==3.7)
+    - Activate this environment (`conda activate <your-env-name>`)
+    - Install the extra dependencies for tests (`pip install kedro-mlflow[tests]`)
     - Apply your changes
     - Run pre-commit (black linting, flake8 errors, isort with ``pre-commit run``)
 4. Submit your changes:
@@ -42,4 +45,4 @@ The current workflow is the following:
 4. Checkout the [publish workflow](https://github.com/Galileo-Galilei/kedro-mlflow/actions?query=workflow%3Apublish) to see if:
     - The package has been uploaded on PyPI sucessfully
     - The changes have been merged back to develop
-5. If the pipeline has failed, please raise an issue to correct the CI, and ensure merge on develop "by hand"".
+5. If the pipeline has failed, please raise an issue to correct the CI, and ensure merge on develop manually.
diff --git a/README.md b/README.md
index 182e8475..bcd8ec47 100644
--- a/README.md
+++ b/README.md
@@ -13,19 +13,19 @@
 ----------------------------------------------------------
 | Branch | Tests | Coverage | Documentation | Deployment | Activity |
 |--------|-------|----------|---------------|------------|------------|
-| `develop`| [![test](https://github.com/Galileo-Galilei/kedro-mlflow/workflows/test/badge.svg?branch=develop)](https://github.com/Galileo-Galilei/kedro-mlflow/actions?query=workflow%3Atest+branch%3Adevelop)| [![codecov](https://codecov.io/gh/Galileo-Galilei/kedro-mlflow/branch/develop/graph/badge.svg)](https://codecov.io/gh/Galileo-Galilei/kedro-mlflow/branch/develop)|[![Documentation](https://readthedocs.org/projects/kedro-mlflow/badge/?version=latest)](https://kedro-mlflow.readthedocs.io/en/latest/)| [![create-release-candidate](https://github.com/Galileo-Galilei/kedro-mlflow/workflows/create-release-candidate/badge.svg?branch=develop)](https://github.com/Galileo-Galilei/kedro-mlflow/actions?query=branch%3Adevelop+workflow%3Acreate-release-candidate)|[![commit](https://img.shields.io/github/commits-since/Galileo-Galilei/kedro-mlflow/0.4.1)](https://github.com/Galileo-Galilei/kedro-mlflow/compare/0.4.1...develop)|
+| `develop`| [![test](https://github.com/Galileo-Galilei/kedro-mlflow/workflows/test/badge.svg?branch=develop)](https://github.com/Galileo-Galilei/kedro-mlflow/actions?query=workflow%3Atest+branch%3Adevelop)| [![codecov](https://codecov.io/gh/Galileo-Galilei/kedro-mlflow/branch/develop/graph/badge.svg)](https://codecov.io/gh/Galileo-Galilei/kedro-mlflow/branch/develop)|[![Documentation](https://readthedocs.org/projects/kedro-mlflow/badge/?version=latest)](https://kedro-mlflow.readthedocs.io/en/latest/)| [![create-release-candidate](https://github.com/Galileo-Galilei/kedro-mlflow/workflows/create-release-candidate/badge.svg?branch=develop)](https://github.com/Galileo-Galilei/kedro-mlflow/actions?query=branch%3Adevelop+workflow%3Acreate-release-candidate)|[![commit](https://img.shields.io/github/commits-since/Galileo-Galilei/kedro-mlflow/0.5.0)](https://github.com/Galileo-Galilei/kedro-mlflow/compare/0.5.0...develop)|
 | `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)|[![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)||
 
 # What is kedro-mlflow?
 
-``kedro-mlflow`` is a [kedro-plugin](https://kedro.readthedocs.io/en/stable/04_user_guide/10_developing_plugins.html) for lightweight and portable integration of [mlflow](https://mlflow.org/docs/latest/index.html) capabilities inside [kedro](https://kedro.readthedocs.io/en/stable/index.html) projects. It enforces [``Kedro`` principles](https://kedro.readthedocs.io/en/stable/12_faq/01_faq.html?highlight=principles#what-is-the-philosophy-behind-kedro) to make mlflow usage as production ready as possible. Its core functionalities are :
+``kedro-mlflow`` is a [kedro-plugin](https://kedro.readthedocs.io/en/stable/07_extend_kedro/04_plugins.html) for lightweight and portable integration of [mlflow](https://mlflow.org/docs/latest/index.html) capabilities inside [kedro](https://kedro.readthedocs.io/en/stable/index.html) projects. It enforces [``Kedro`` principles](https://kedro.readthedocs.io/en/stable/12_faq/01_faq.html?highlight=principles#what-is-the-philosophy-behind-kedro) to make mlflow usage as production ready as possible. Its core functionalities are :
 
 - **versioning**: `kedro-mlflow` intends to enhance reproducibility for machine learning experimentation. With `kedro-mlflow` installed, you can effortlessly register your parameters or your datasets with minimal configuration in a kedro run. Later, you will be able to browse your runs in the mlflow UI, and retrieve the runs you want. This is directly linked to [Mlflow Tracking](https://www.mlflow.org/docs/latest/tracking.html).
 - **model packaging**: ``kedro-mlflow`` intends to be be an agnostic machine learning framework for people who want to write portable, production ready machine learning pipelines. It offers a convenient API to convert a Kedro pipeline to a ``model`` in the mlflow sense. Consequently, you can *API-fy* or serve your Kedro pipeline with one line of code, or share a model with without worrying of the preprocessing to be made for further use. This is directly linked to [Mlflow Models](https://www.mlflow.org/docs/latest/models.html).
 
 # How do I install kedro-mlflow?
 
-**Important: kedro-mlflow is only compatible with ``kedro>=0.16.0``. If you have a project created with an older version of ``Kedro``, see this [migration guide](https://github.com/quantumblacklabs/kedro/blob/master/RELEASE.md#migration-guide-from-kedro-015-to-016).**
+**Important: ``kedro-mlflow`` is only compatible with ``kedro>=0.16.0``. If you have a project created with an older version of ``Kedro``, see this [migration guide](https://github.com/quantumblacklabs/kedro/blob/master/RELEASE.md#migration-guide-from-kedro-015-to-016).**
 
 ``kedro-mlflow`` is available on PyPI, so you can install it with ``pip``:
 
@@ -39,19 +39,20 @@ If you want to use the ``develop`` version of the package which is the most up t
 pip install --upgrade git+https://github.com/Galileo-Galilei/kedro-mlflow.git@develop
 ```
 
-I strongly recommend to use ``conda`` (a package manager) to create an environment and to read [``kedro`` installation guide](https://kedro.readthedocs.io/en/stable/02_getting_started/01_prerequisites.html).
+I strongly recommend to use ``conda`` (a package manager) to create an environment and to read [``kedro`` installation guide](https://kedro.readthedocs.io/en/latest/02_get_started/02_install.html).
 
 # Getting started
 
 The documentation contains:
 
-- [A "hello world" example](https://kedro-mlflow.readthedocs.io/en/latest/source/02_hello_world_example/index.html) which demonstrates how you to **setup your project**, **version parameters** and **datasets**, and browse your runs in the UI.
-- A more [detailed tutorial](https://kedro-mlflow.readthedocs.io/en/latest/source/03_tutorial/index.html) to show more advanced features (mlflow configuration through the plugin, package and serve a kedro ``Pipeline``...)
+- [A  "hello world" example](https://kedro-mlflow.readthedocs.io/en/latest/source/03_getting_started/index.html) which demonstrates how you to **setup your project**, **version parameters** and **datasets**, and browse your runs in the UI.
+- A section for [advanced machine learning versioning](https://kedro-mlflow.readthedocs.io/en/latest/source/04_experimentation_tracking/index.html) to show more advanced features (mlflow configuration through the plugin, package and serve a kedro ``Pipeline``...)
+- A section to demonstrate how to use `kedro-mlflow` as a [machine learning framework](https://kedro-mlflow.readthedocs.io/en/latest/source/05_framework_ml/index.html) to deliver production ready pipelines and serve them. This section comes with [an example repo](https://github.com/Galileo-Galilei/kedro-mlflow-tutorial) you can clone and try out.
 
 Some frequently asked questions on more advanced features:
 
-- You want to log additional metrics to the run? -> [Try ``MlflowMetricsDataSet``](https://kedro-mlflow.readthedocs.io/en/latest/source/03_tutorial/07_version_metrics.html) !
-- You want to log nice dataviz of your pipeline that you register with ``MatplotlibWriter``? -> [Try ``MlflowArtifactDataSet`` to log any local files (.png, .pkl, .csv...) *automagically*](https://kedro-mlflow.readthedocs.io/en/latest/source/02_hello_world_example/02_first_steps.html#artifacts)!
+- You want to log additional metrics to the run? -> [Try ``MlflowMetricsDataSet``](https://kedro-mlflow.readthedocs.io/en/stable/source/04_experimentation_tracking/05_version_metrics.html) !
+- You want to log nice dataviz of your pipeline that you register with ``MatplotlibWriter``? -> [Try ``MlflowArtifactDataSet`` to log any local files (.png, .pkl, .csv...) *automagically*](https://kedro-mlflow.readthedocs.io/en/stable/source/04_experimentation_tracking/03_version_datasets.html)!
 - You want to create easily an API to share your awesome model to anyone? -> [See if ``pipeline_ml_factory`` can fit your needs](https://github.com/Galileo-Galilei/kedro-mlflow/issues/16)
 - You want to do something that is not straigthforward with current implementation? Open an issue, and let's see what happens!
 
@@ -65,13 +66,13 @@ This package is still in active development. We use [SemVer](https://semver.org/
 
 The user must be aware that we will not reach `1.0.0` milestone before Kedro does (mlflow has already reached `1.0.0`).
 
-If you want to see how to migrate from one version of `kedro-mlflow` to another, see the [migration guide](docs/source/03_tutorial/00_migration_guide.md).
+If you want to see how to migrate from one version of `kedro-mlflow` to another, see the [migration guide](https://kedro-mlflow.readthedocs.io/en/stable/source/02_installation/03_migration_guide.html).
 
 # Can I contribute?
 
-We'd be happy to receive help to maintain and improve the package. Please check the [contributing guidelines](https://github.com/Galileo-Galilei/kedro-mlflow/blob/develop/CONTRIBUTING.md).
+We'd be happy to receive help to maintain and improve the package. Any PR will be considered (from typo in the docs to core features add-on) Please check the [contributing guidelines](https://github.com/Galileo-Galilei/kedro-mlflow/blob/develop/CONTRIBUTING.md).
 
-#### Main contributors
+# Main contributors
 
 The following people actively maintain, enhance and discuss design to make this package as good as possible. Many thanks to them!
 
diff --git a/docs/index.rst b/docs/index.rst
index caf31143..8a5e5de3 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,4 +1,4 @@
-.. kedro-mlflow documentation master file, created by
+.. ``kedro-mlflow`` documentation master file, created by
    sphinx-quickstart on Mon Jul 13 14:21:13 2020.
    You can adapt this file completely to your liking, but it should at least
    contain the root `toctree` directive.
@@ -12,9 +12,8 @@ Welcome to kedro-mlflow's documentation!
    Introduction <source/01_introduction/index.rst>
    Installation <source/02_installation/index.rst>
    Getting Started <source/03_getting_started/index.rst>
-   Advanced versioning of machine learning experimentations <source/04_experimentation_tracking/index.rst>
-   A comprehensive framework to deliver machine learning pipelines <source/05_framework_ml/index.rst>
-   Advanced capabilities <source/06_advanced_use/index.rst>
+   Advanced machine learning versioning <source/04_experimentation_tracking/index.rst>
+   A mlops framework for efficient deployment <source/05_framework_ml/index.rst>
    Python objects <source/07_python_objects/index.rst>
 
 
diff --git a/docs/source/01_introduction/01_introduction.md b/docs/source/01_introduction/01_introduction.md
index 55c0c09b..17121c13 100644
--- a/docs/source/01_introduction/01_introduction.md
+++ b/docs/source/01_introduction/01_introduction.md
@@ -47,7 +47,7 @@ We discuss hereafter how the two libraries compete on the different functionalit
 
 ### Versioning: Kedro 1 - 1 Mlflow
 
-The ``Kedro`` [``Journal`` aims at reproducibility](https://kedro.readthedocs.io/en/stable/04_user_guide/13_journal.html), but is not focused on machine learning. The `Journal` keeps track of two elements:
+The ``Kedro`` [``Journal`` aims at reproducibility](https://kedro.readthedocs.io/en/latest/kedro.versioning.Journal.html), but is not focused on machine learning. The `Journal` keeps track of two elements:
 
 - the CLI arguments, including *on the fly* parameters. This makes the command used to run the pipeline fully reproducible.
 - the ``AbstractVersionedDataSet`` for which versioning is activated. It consists in copying the data whom ``versioned`` argument is ``True`` when the ``save`` method of the ``AbstractVersionedDataSet`` is called.
diff --git a/docs/source/01_introduction/02_motivation.md b/docs/source/01_introduction/02_motivation.md
index ca4c082d..68144513 100644
--- a/docs/source/01_introduction/02_motivation.md
+++ b/docs/source/01_introduction/02_motivation.md
@@ -33,7 +33,7 @@ Above implementations have the advantage of being very straightforward and *mlfl
   - it is **hard to modify** (if you want to remove / add / modify an mlflow action you have to find it in the code)
   - it **prevents reuse** (re-usable function must not contain mlflow specific code unrelated to their functional specificities, only their execution must be tracked).
 
-``kedro-mlflow`` enforces these best practices while implementing a clear interface for each mlflow action in Kedro template. Below chart maps the mlflow action to perform with the Python API provided by kedro-mlflow and the location in Kedro template where the action should be performed.
+``kedro-mlflow`` enforces these best practices while implementing a clear interface for each mlflow action in Kedro template. Below chart maps the mlflow action to perform with the Python API provided by ``kedro-mlflow`` and the location in Kedro template where the action should be performed.
 
 |Mlflow action                |Template file           |Python API                                        |
 |:----------------------------|:-----------------------|:------------------------------------------------------|
@@ -44,6 +44,6 @@ Above implementations have the advantage of being very straightforward and *mlfl
 |Logging metrics              |``catalog.yml``    |``MlflowMetricsDataSet``                          |
 |Logging Pipeline as model    |``hooks.py``    |``KedroPipelineModel`` and ``pipeline_ml_factory``|
 
-In the current version (``kedro_mlflow=0.4.1``), `kedro-mlflow` does not provide interface to set tags outside a Kedro ``Pipeline``. Some of above decisions are subject to debate and design decisions (for instance, metrics are often updated in a loop during each epoch / training iteration and it does not always make sense to register the metric between computation steps, e.g. as a an I/O operation after a node run).
+In the current version (``kedro_mlflow=0.5.0``), `kedro-mlflow` does not provide interface to set tags outside a Kedro ``Pipeline``. Some of above decisions are subject to debate and design decisions (for instance, metrics are often updated in a loop during each epoch / training iteration and it does not always make sense to register the metric between computation steps, e.g. as a an I/O operation after a node run).
 
-_**Note:** the version ``0.4.1`` does not need any ``MLProject`` file to use mlflow inside your Kedro project. As seen in the [introduction](./01_introduction.md), this file overlaps with Kedro configuration files._
+_**Note:** the version ``0.5.0`` does not need any ``MLProject`` file to use mlflow inside your Kedro project. As seen in the [introduction](./01_introduction.md), this file overlaps with Kedro configuration files._
diff --git a/docs/source/02_installation/01_installation.md b/docs/source/02_installation/01_installation.md
index 92a35a45..0704ae23 100644
--- a/docs/source/02_installation/01_installation.md
+++ b/docs/source/02_installation/01_installation.md
@@ -6,7 +6,7 @@
 
 I strongly recommend to use ``conda`` (a package manager) to create an environment in order to avoid version conflicts between packages.
 
-I also recommend to read [Kedro installation guide](https://kedro.readthedocs.io/en/stable/02_getting_started/01_prerequisites.html) to set up your Kedro project.
+I also recommend to read [Kedro installation guide](https://kedro.readthedocs.io/en/latest/02_get_started/02_install.html) to set up your Kedro project.
 
 ```console
 conda create -n <your-environment-name> python=<3.[6-8].X>
@@ -77,10 +77,10 @@ projects. It is developed as part of
 the Kedro initiative at QuantumBlack.
 
 Installed plugins:
-kedro_mlflow: 0.4.1 (hooks:global,project)
+kedro_mlflow: 0.5.0 (hooks:global,project)
 ```
 
-The version ``0.4.1`` of the plugin is installed and has both global and project commands.
+The version ``0.5.0`` 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/02_installation/02_setup.md b/docs/source/02_installation/02_setup.md
index af69daba..7de0bd94 100644
--- a/docs/source/02_installation/02_setup.md
+++ b/docs/source/02_installation/02_setup.md
@@ -4,7 +4,7 @@ This section assume that [you have installed `kedro-mlflow` in your virtual envi
 
 ## Create a kedro project
 
-This plugin must be used in an existing kedro project. If you do not have a kedro project yet, you can create it with ``kedro new`` command. [See the kedro docs for a tutorial](https://kedro.readthedocs.io/en/latest/02_getting_started/03_new_project.html).
+This plugin must be used in an existing kedro project. If you do not have a kedro project yet, you can create it with ``kedro new`` command. [See the kedro docs for a tutorial](https://kedro.readthedocs.io/en/latest/02_get_started/04_new_project.html).
 
 If you do not have a real-world project, you can use a kedro example and [follow the "Getting started" example](../03_getting_started/01_example_project.md) to make a demo of this plugin out of the box.
 
@@ -12,9 +12,9 @@ If you do not have a real-world project, you can use a kedro example and [follow
 
 In order to use the ``kedro-mlflow`` plugin, you need to setup its configuration and declare its hooks. Those 2 actions are detailled in the following paragraphs.
 
-### Setting up the kedro-mlflow configuration file
+### Setting up the ``kedro-mlflow`` configuration file
 
-``kedro-mlflow`` is [configured](../07_python_objects/05_Configuration.md) through an ``mlflow.yml`` file. The recommended way to initialize the `mlflow.yml` is by using [the kedro-mlflow CLI](../07_python_objects/04_CLI.md). **It is mandatory for the plugin to work.**
+``kedro-mlflow`` is [configured](../07_python_objects/05_Configuration.md) through an ``mlflow.yml`` file. The recommended way to initialize the `mlflow.yml` is by using [the ``kedro-mlflow`` CLI](../07_python_objects/04_CLI.md). **It is mandatory for the plugin to work.**
 
 Set the working directory at the root of your kedro project (i.e. the folder with the ``.kedro.yml`` file)
 
@@ -31,18 +31,24 @@ kedro mlflow init
 you should see the following message:
 
 ```console
-'conf/base/mlflow.yml' successfully updated.
+'conf/local/mlflow.yml' successfully updated.
 ```
 
-### Declaring kedro-mlflow hooks
+*Note: you can create the configuration file in another kedro environment with the `--env` argument:*
 
-``kedro_mlflow`` hooks implementations must be registered with Kedro. There are three ways of registring [hooks](https://kedro.readthedocs.io/en/latest/07_extend_kedro/04_hooks.html?highlight=hooks).
+```console
+kedro mlflow init --env=<other-environment>
+```
+
+### Declaring ``kedro-mlflow`` hooks
+
+``kedro_mlflow`` hooks implementations must be registered with Kedro. There are three ways of registering [hooks](https://kedro.readthedocs.io/en/latest/07_extend_kedro/02_hooks.html).
 
 **Note that you must register the two hooks provided by kedro-mlflow** (``MlflowPipelineHook`` and ``MlflowNodeHook``) for the plugin to work.
 
 #### Declaring hooks through auto-discovery (for `kedro>=0.16.4`) [Default behaviour]
 
-If you use `kedro>=0.16.4`, `kedro-mlflow` hooks are auto-registered automatically by default without any action from your side. You can [disable this behaviour](https://kedro.readthedocs.io/en/latest/07_extend_kedro/04_hooks.html#disable-auto-registered-plugins-hooks) in your `.kedro.yml` or your `pyproject.toml` file.
+If you use `kedro>=0.16.4`, `kedro-mlflow` hooks are auto-registered automatically by default without any action from your side. You can [disable this behaviour](https://kedro.readthedocs.io/en/latest/07_extend_kedro/02_hooks.html#disable-auto-registered-plugins-hooks) in your `.kedro.yml` or your `pyproject.toml` file.
 
 #### Declaring hooks through code, in ``ProjectContext`` (for `kedro>=0.16.0, <=0.16.3`)
 
diff --git a/docs/source/02_installation/03_migration_guide.md b/docs/source/02_installation/03_migration_guide.md
index 149113e0..5f1d8b61 100644
--- a/docs/source/02_installation/03_migration_guide.md
+++ b/docs/source/02_installation/03_migration_guide.md
@@ -6,7 +6,7 @@ This page explains how to migrate an existing kedro project to a more up to date
 
 ### Catalog entries
 
-Replace the follwoing entries:
+Replace the following entries:
 
 |old                                    |new                                              |
 |:--------------------------------------|:------------------------------------------------|
diff --git a/docs/source/03_getting_started/01_example_project.md b/docs/source/03_getting_started/01_example_project.md
index 40eaa32a..1f6f6d06 100644
--- a/docs/source/03_getting_started/01_example_project.md
+++ b/docs/source/03_getting_started/01_example_project.md
@@ -7,12 +7,12 @@ Create a conda environment and install ``kedro-mlflow`` (this will automatically
 ```console
 conda create -n km_example python=3.6.8 --yes
 conda activate km_example
-pip install kedro-mlflow==0.4.1
+pip install kedro-mlflow==0.5.0
 ```
 
 ## Install the toy project
 
-For this end to end example, we will use the [kedro starter](https://kedro.readthedocs.io/en/latest/02_getting_started/05_starters.html#creating-new-projects-with-kedro-starters) with the [iris dataset](https://github.com/quantumblacklabs/kedro-starter-pandas-iris).
+For this end to end example, we will use the [kedro starter](https://kedro.readthedocs.io/en/latest/02_get_started/06_starters.html) with the [iris dataset](https://github.com/quantumblacklabs/kedro-starter-pandas-iris).
 
 We use this project because:
 
diff --git a/docs/source/03_getting_started/02_first_steps.md b/docs/source/03_getting_started/02_first_steps.md
index f35a0c71..907690c6 100644
--- a/docs/source/03_getting_started/02_first_steps.md
+++ b/docs/source/03_getting_started/02_first_steps.md
@@ -11,10 +11,10 @@ kedro mlflow init
 You will see the following message:
 
 ```console
-'conf/base/mlflow.yml' successfully updated.
+'conf/local/mlflow.yml' successfully updated.
 ```
 
-The ``conf/base`` folder is updated and you can see the `mlflow.yml` file:
+The ``conf/local`` folder is updated and you can see the `mlflow.yml` file:
 
 ![initialized_project](../imgs/initialized_project.png)
 
diff --git a/docs/source/03_getting_started/index.rst b/docs/source/03_getting_started/index.rst
index 3f030bf0..90ad871d 100644
--- a/docs/source/03_getting_started/index.rst
+++ b/docs/source/03_getting_started/index.rst
@@ -6,4 +6,4 @@ Introduction
 
    Goal of the tutorial <00_intro_tutorial.md>
    Create an example project <01_example_project.md>
-   First steps with kedro-mlflow <02_first_steps.md>
+   First steps with ``kedro-mlflow`` <02_first_steps.md>
diff --git a/docs/source/04_experimentation_tracking/01_configuration.md b/docs/source/04_experimentation_tracking/01_configuration.md
index 366070e7..2b253e7b 100644
--- a/docs/source/04_experimentation_tracking/01_configuration.md
+++ b/docs/source/04_experimentation_tracking/01_configuration.md
@@ -89,10 +89,17 @@ hooks:
     flatten_dict_params: False  # if True, parameter which are dictionary will be splitted in multiple parameters when logged in mlflow, one for each key.
     recursive: True  # Should the dictionary flattening be applied recursively (i.e for nested dictionaries)? Not use if `flatten_dict_params` is False.
     sep: "." # In case of recursive flattening, what separator should be used between the keys? E.g. {hyperaparam1: {p1:1, p2:2}}will be logged as hyperaparam1.p1 and hyperaparam1.p2 oin mlflow.
+    long_parameters_strategy: fail  # One of ["fail", "tag", "truncate" ] If a parameter is above mlflow limit (currently 250), what should ``kedro-mlflow`` do? -> fail, set as a tag instead of a parameter, or truncate it to its 250 first letters?
 ```
 
 If you set `flatten_dict_params` to `True`, each key of the dictionary will be logged as a mlflow parameters, instead of a single parameter for the whole dictionary. Note that it is recommended to facilitate run comparison.
 
+The `long_parameters_strategy` key enable to define different way to handle parameters over the mlflow limit (currently 250 characters):
+
+- `fail`: no special management of characters above the limit. They will be send to mlflow and as a result, in some backend they will be stored normally ([e.g. for FileStore backend](https://github.com/mlflow/mlflow/issues/2814#issuecomment-628284425)) and for some others logging will fail.
+- `truncate`: All parameters above the limit will be automatically truncated to a 250-character length to make sure logging will pass for all mlflow backend.
+- `tag`: Any parameter above the limit will be registered as a tag instead of a parameter as it seems to be the [recommended mlflow way to deal with long parameters](https://github.com/mlflow/mlflow/issues/1976).
+
 ### Configure the user interface
 
 You can configure mlflow user interface default params inside the `mlflow.yml`:
diff --git a/docs/source/04_experimentation_tracking/02_version_parameters.md b/docs/source/04_experimentation_tracking/02_version_parameters.md
index 73a7aff4..f3348f49 100644
--- a/docs/source/04_experimentation_tracking/02_version_parameters.md
+++ b/docs/source/04_experimentation_tracking/02_version_parameters.md
@@ -2,7 +2,7 @@
 
 ## Automatic parameters versioning
 
-Parameters versioning is automatic when the ``MlflowNodeHook`` is added to [the hook list of the ``ProjectContext``](../02_installation/02_setup.md#declaring-kedro-mlflow-hooks). In ``kedro-mlflow==0.4.1``, the `mlflow.yml` configuration file has a parameter called ``flatten_dict_params`` which enables to [log as distinct parameters the (key, value) pairs of a ```Dict`` parameter](../07_python_objects/02_Hooks.md).
+Parameters versioning is automatic when the ``MlflowNodeHook`` is added to [the hook list of the ``ProjectContext``](../02_installation/02_setup.md#declaring-kedro-mlflow-hooks). In ``kedro-mlflow==0.5.0``, the `mlflow.yml` configuration file has a parameter called ``flatten_dict_params`` which enables to [log as distinct parameters the (key, value) pairs of a ```Dict`` parameter](../07_python_objects/02_Hooks.md).
 
 You **do not need any additional configuration** to benefit from parameters versioning.
 
@@ -26,7 +26,7 @@ The hook **detects parameters through their prefix ``params:`` or the value ``pa
 
 ### How can I register a parameter if I use a ``TemplatedConfigLoader``?
 
-If you [use a ``TemplatedConfigLoader``](https://kedro.readthedocs.io/en/stable/04_user_guide/03_configuration.html?highlight=TemplatedConfigLoader#templating-configuration) to enable dynamic parameters contruction at runtime or dependency between configuration files, and if we assume your ``src/<project-name>/run.py`` file looks like:
+If you [use a ``TemplatedConfigLoader``](https://kedro.readthedocs.io/en/latest/04_kedro_project_setup/02_configuration.html#templating-configuration) to enable dynamic parameters contruction at runtime or dependency between configuration files, and if we assume your ``src/<project-name>/run.py`` file looks like:
 
 ```python
 from kedro.config import TemplatedConfigLoader  # new import
diff --git a/docs/source/04_experimentation_tracking/03_version_datasets.md b/docs/source/04_experimentation_tracking/03_version_datasets.md
index 1ba7400b..21a96495 100644
--- a/docs/source/04_experimentation_tracking/03_version_datasets.md
+++ b/docs/source/04_experimentation_tracking/03_version_datasets.md
@@ -1,4 +1,5 @@
 # Versioning Kedro DataSets
+
 ## What is artifact tracking?
 
 Mlflow defines artifacts as "any data a user may want to track during code execution". This includes, but is not limited to:
@@ -13,7 +14,7 @@ Artifacts are a very flexible and convenient way to "bind" any data type to your
 
 ## How to version data in a kedro project?
 
-kedro-mlflow introduces a new ``AbstractDataSet`` called ``MlflowArtifactDataSet``. It is a wrapper for any ``AbstractDataSet`` which decorates the underlying dataset ``save`` method and logs the file automatically in mlflow as an artifact each time the ``save`` method is called.
+``kedro-mlflow`` introduces a new ``AbstractDataSet`` called ``MlflowArtifactDataSet``. It is a wrapper for any ``AbstractDataSet`` which decorates the underlying dataset ``save`` method and logs the file automatically in mlflow as an artifact each time the ``save`` method is called.
 
 Since it is an ``AbstractDataSet``, it can be used with the YAML API. Assume that you have the following entry in the ``catalog.yml``:
 
@@ -77,7 +78,7 @@ Setting the `mlflow_tracking_uri` key of `mlflow.yml` to the url of this server
 
 You can refer to [this issue](https://github.com/Galileo-Galilei/kedro-mlflow/issues/15) for further details.
 
-In ``kedro-mlflow==0.4.1`` you must configure these elements by yourself. Further releases will introduce helpers for configuration.
+In ``kedro-mlflow==0.5.0`` you must configure these elements by yourself. Further releases will introduce helpers for configuration.
 
 ### Can I log an artifact in a specific run?
 
diff --git a/docs/source/04_experimentation_tracking/04_version_models.md b/docs/source/04_experimentation_tracking/04_version_models.md
index a3dfc033..bea29840 100644
--- a/docs/source/04_experimentation_tracking/04_version_models.md
+++ b/docs/source/04_experimentation_tracking/04_version_models.md
@@ -21,7 +21,7 @@ my_sklearn_model:
     flavor: mlflow.sklearn
 ```
 
-More informations on available parameters are available in the [dedicated section](docs\source\05_python_objects\01_DataSets.md#mlflowmodelloggerdataset).
+More informations on available parameters are available in the [dedicated section](../07_python_objects/01_DataSets.md#mlflowmodelloggerdataset).
 
 You are now able to use ``my_sklearn_model`` in your nodes. Since this model is registered in mlflow, you can also leverage the [mlflow model serving abilities](https://www.mlflow.org/docs/latest/cli.html#mlflow-models-serve) or [predicting on batch abilities](https://www.mlflow.org/docs/latest/cli.html#mlflow-models-predict), as well as the [mlflow models registry](https://www.mlflow.org/docs/latest/model-registry.html) to manage the lifecycle of this model.
 
diff --git a/docs/source/04_experimentation_tracking/06_mlflow_ui.md b/docs/source/04_experimentation_tracking/06_mlflow_ui.md
index 70224ba3..1dfe5e9b 100644
--- a/docs/source/04_experimentation_tracking/06_mlflow_ui.md
+++ b/docs/source/04_experimentation_tracking/06_mlflow_ui.md
@@ -4,7 +4,7 @@
 
 Mlflow offers a user interface (UI) that enable to browse the run history.
 
-## The kedro-mlflow helper
+## The ``kedro-mlflow`` helper
 
 When you use a local storage for kedro mlflow, you can call a [mlflow cli command](https://www.mlflow.org/docs/latest/quickstart.html#viewing-the-tracking-ui) to launch the UI if you do not have a [mlflow tracking server configured](https://www.mlflow.org/docs/latest/tracking.html#tracking-ui).
 
diff --git a/docs/source/05_framework_ml/01_why_framework.md b/docs/source/05_framework_ml/01_why_framework.md
new file mode 100644
index 00000000..06f70858
--- /dev/null
+++ b/docs/source/05_framework_ml/01_why_framework.md
@@ -0,0 +1,141 @@
+# Why we need a mlops framework to manage machine learning development lifecycle
+
+## Machine learning deployment is hard because it comes with a lot of constraints and no adequate tooling
+
+### Identifying the challenges to address when deploying machine learning
+
+It is a very common pattern to hear that "machine learning deployment is hard", and this is supposed to explain why so many firms do not achieve to insert ML models in their IT systems (and consequently, not make money despite consequent investments in ML).
+
+On the other hand, you can find thousands of tutorial across the web to explain how to deploy a ML API in 5 mn, either locally or on the cloud. There is also a large amount of training sessions which can teach you "how to become a machine learning engineer in 3 months".
+
+*Who is right then? Both!*
+
+Actually, there is a confusion on what "deployment" means, especially in big enterprises that are not "tech native" or for newbies in ML world. Serving a model over an API pattern is a start, but you often need to ensure (at least) the following properties for your system:
+
+- **scalability and cost control**: in many cases, you need to be able to deal with a lot of (possibly concurrent) requests (likely much more than during training phase). It may be hard to ensure that the app will be able to deal with such an important amount of data. Another issue is that ML oftens needs specific infrastrusture (e.g., GPU's) which are very expensive. Since the request against the model often vary wildly over time, it may be important to adapt the infrastructure in real time to avoid a lot of infrastructure costs
+- **speed**: A lot of recent *state of the art* (SOTA) deep learning / ensemble models are computationally heavy and this may hurt inference speed, which is critical for some systems.
+- **disponibility and resilience**: Machine learning systems are more complex than traditional softwares because they have more moving parts (i.e. data and parameters). This increases the risk of errors, and since ML systems are used for critical systems making both the infrastructure and the code robust is key.
+- **portability / ease of integration with external components**: ML models are not intended to be directly used by the end users, but rather be consumed by another part of your system (e.g a call to an API). To speed up deployment, your model must be easy to be consumed, i.e. *as self contained as possible*. As a consequence, you must **deploy a ML pipeline which hanbles business objects instead of only a ML model**. If the other part which consumes your API needs to make a lot of data preprocessing *before* using your model, it makes it:
+  - very risky to use, because preprocessing and model are decoupled: any change in your model must be reflected in this other data pipeline and there is a huge mismatch risk when redeploying
+  - slow and costful to deploy because each deployment of your model needs some new development on the client side
+  - poorly reusable beacuse each new app who wants to use the model needs some specific development on its own
+- **reproducibility**: the ML development is very iterative by nature. You often try a simple baseline model and iterate (sometimes directly by discussing with the final user) to finally get the model that suits the most your needs ( wich is the balance between speed / accuracy / interpretability / maintenance costs / inference costs / data availability / labelling costs...). Looping through these iterations, it is very easy to forget what was the best model. You should not rely only on your memory to compare your experiments. Moreover, many countries have regulatory constraints to avoid discriminations (e.g. GDPR in the EU), and your must be able to justify how your model was built and to that extent reproducibility is key. It is also essential when you redeploy a model that turns out to be worse than the old one and you have to rollback fast.
+- **monitoring and ease of redeployment**: It is well known that model quality decay over time (sometimes quickly!), and if you use ML for a critical activity, you will have to retrain your model periodically to maintain its performance. It is critical to be able to redeploy easily your model. This implies that retraining (or even redeployment of a new model) must be as **automated as possible** to ensure deployment speed and model quality.
+
+An additional problem that happens in real world is that it is sometimes poorly understood by end users that the ML model is not standalone. It implies
+**external developments** from the client side (at least to call the model, sometimes to adapt a data pipeline or to change the user interface to add the ML functionality) and they have hard times to understand the entire costs of a ML project as well as their responsibilities in the project. This is not really a problem of ML but rather on how to give a minimal culture on ML to business end users.
+
+### A comparison between traditional software development and machine learning projects
+
+#### ML and traditional software have different moving parts
+
+A traditional software project contains several moving parts:
+
+- code
+- environment (packages versions...)
+- infrastructure (build on Windows, deploy on linux)
+
+Since it is a more mature industry, efficient tools exists to manage these items (Git, pip/conda, infra as code...). On the other hand, a ML project has additional moving parts:
+
+- parameters
+- data
+
+As ML is a much less mature field, efficient tooling to adress these items are very recent and not completely standardized yet (e.g. Mlflow to track parameters, DVC to version data, `great-expectations` to monitor data which go through your pipelines, `tensorboard` to monitor your model metrics...)
+
+> **Mlflow is one of the most mature tool to manage these new moving parts.**
+
+#### ML and traditional software have different development lifecycles
+
+In traditional software, the development workflow is roughly the following:
+
+- you create a git branch
+- you develop your new feature
+- you add tests and ensure there are no regression
+- you merge the branch on the main branch to add your feature to the codebase
+
+This is a **linear** development process based on **determinist** behaviour of the code.
+
+However in ML development, the workflow is much more iterative and may looklike this:
+
+- you create a notebook
+- you make some exploration on the data with some descriptive analysis and a baseline model
+- you switch to a git branch and python scripts once the model is quite stable
+- you retrain the model, eventually do some parameter tuning
+- you merge your code on the main branch to share your work
+
+If you need to modify the model later (do a different preprocessing, change the model type...), you do not **add** code to the codebase, you **modify** the existing code. This makes unit testing much harder because the desired features change over time.
+
+The other difficulty when testing machine learning applications is it hard to test for regression, since the model depends on underlying data and the chosen metrics. If a new model performs slightly better or worse than the previous one ont the same dataset, it may be due to randomness and not to code quality. If the metric varies on a different dataset, it is even harder to know if it is due to code quality or to innate randomness.
+
+This is a **cyclic** development process based on a **stochastic** behaviour of the code.
+
+> **Kedro is a very new tool and cannot be called "mature" at this stage but tries to solve this development lifecyle with a very fluent API to create and modify machine learning pipelines.**
+
+## Deployment issues addressed by `kedro-mlflow` and their solutions
+
+### Out of scope
+
+We will focus on machine learning *development* lifecycle. As a consequence, these items are out of scope:
+
+- Orchestration
+- Issues related to infrastructure (related, but not limited to, the 3 first items of above list: scalability and cost control, inference speed, disponibility and resilience)
+- Issues related to model monitoring: Data distribution changes over time, model decay, data access...
+
+### Issue 1: The training process is poorly reproducible
+
+The main reason which explains why training is hard to reproduce is the iterative process. Data scientists launch several times the same run with slightly different parameters / data / preprocessing. If they rely on their memory to compare these runs, they will likely struggle to remember what was the best one.
+
+> **`kedro-mlflow` offers automatic parameters versioning** when a pipeline is ran to easily link a model to its training parameters.
+
+Note that there is also a lot of "innate" randomness in ML pipelines and if a seed is not set explictly as a parameter , the run will likely not be reproducible (separation train/test/validation, moving underlying data sources, random initialisation for optimizers, random split for bootstrap...).
+
+### Issue 2: The data scientist and stakeholders focus on training
+
+While building the ML model, the inference pipeline is often completely ignored by the data scientist. The best example are Kaggle competitions where a very common workflow is the following:
+
+- merge the training and the test data at the beginning of their script
+- do the preprocessing on the entire dataset
+- resplit just before training the model
+- train the model on training data
+- predict on test data
+- anayze their metrics, finetune their hyper parameters
+- submit their predictions as data (i.e. as a file) to Kaggle
+
+The very important issue which arises with such a workflow is that **you completely ignore the non reproducibility which arises from the preprocessing (encoding, randomness...)**. Most Kaggle solutions are never tested on an end to end basis, i.e. by running the inference pipeline from the test data input file to the predictions file. This facilitates very bad coding practices and teaches beginner data scientists bad software engineering practice.
+
+> `kedro-mlflow` enables to log the inference pipeline as a Mlflow Model (through a `KedroPipelineModel` class) to ensure that you deploy the inference pipeline as a whole.
+
+### Issue 3: Inference and training are entirely decoupled
+
+As stated previous paragraph, the inference pipeline is not a primary concern when experimenting and developing your model. This raises strong reproducibility issues. Assume that you have logged the model and all its parameters when training (which is a good point!), you will still need to retrieve the code used during training to create the inference pipeline. This is in my experience quite difficult:
+
+- in the best case, you have trained the model from a git sha which is logged in mlflow. Any potential user can (but it takes time) recreate the exact inference pipeline from your source code, and retrieve all necessary artifacts from mlflow. This is tedious, error prone, and gives a lot of responsibility and work to your end user, but at least it makes your model usable.
+- most likely, you did not train your model from a version control commit. While experimenting /debug, it is very common to modify the code and retrain without committing. The exact code associated to a given model will likely be impossible to find out later.  
+
+> `kedro-mlflow` offers a `PipelineML` (and its helpers `pipeline_ml_factory`) class which binds the `training` and `inference` pipeline, and a hook which autolog such pipelines when they are run. This enables data scientists to ensure that each training model is logged with its associated inference pipeline, and is ready to use for any end user. This decreases a lot the necessary cognitive complexity to ensure coherence between training and inference.
+
+### Issue 4: Data scientists do not handle business objects
+
+It is often said that data scientists deliver machine learning *models*. This assumes that all the preprocessing will be recoded the end user of your model. This is a major cause of poor adoption of your model in an enterprise setup because it makes your model:
+
+- hard to use (developments are need on the client side)
+- hard to update (it needs code update from the end user)
+- very error prone (never trust the client!)
+
+If you struggle representing it, imagine that you have developed a NLP model. Would you really ask your end user to give you a one-hot encoded matrix or BERT-tokenized texts with your custom embbeddings and vocabulary?
+
+Your model must handle business objects (e.g. a mail, a movie review, a customer with its characteristic, a raw image...) to be usable.
+
+> Kedro `Pipeline`'s are able to handle processing from the business object to the prediction. Your real model must be a `Pipeline`, and the `KedroPipelineModel` of `kedro-mlflow` helps to store them and log them in mlflow. Additionally, `kedro-mlflow` suggests how your project should be organized in "apps" to make this transition easy.
+
+### Overcoming these problems: support an organisational solution with an efficient tool
+
+``kedro-mlflow`` assume that we declare a clear contrat of what the output of the data science project is: it is an an inference pipeline. This defines a clear "definition of done" of the data science project: is it ready to deploy?
+
+The downside of such an approach is that it increases data scientist's responsibilities,because s(he) is responsible for his code.
+
+``kedro-mlflow`` offers a very convenient way (through the ``pipeline_ml_factory`` function) to make sure that each experiment will result in creating a compliant "output".
+
+This is very transparent for the data scientist who have no extra constraints (apart from developing in Kedro) to respect this contract. Hence, the data scientist still benefits from the interactivity he needs to work. This is why we want to leverage Kedro which is very flexible and offers a convenient way to transition from notebooks to pipeline, and leverage Mlflow for standardising the definiton of an "output" of a datscience project.
+
+> Enforcing these solutions with a tool: `kedro-mlflow` at the rescue
diff --git a/docs/source/05_framework_ml/02_ml_project_components.md b/docs/source/05_framework_ml/02_ml_project_components.md
new file mode 100644
index 00000000..0bba035e
--- /dev/null
+++ b/docs/source/05_framework_ml/02_ml_project_components.md
@@ -0,0 +1,73 @@
+# The components of a machine learning application
+
+## Definition: apps of a machine learning projects
+
+A machine learning project is composed of 3 main blocks that I will call "apps" in the rest of the paragraph. These 3 apps are:
+
+- The *etl_app*, which is the application in charge of bringing the data to the machine learning pipeline
+- The *ml_app*, which is the application in charge of managing the machine learning model (including training and inference)
+- The *user_app* which is the application in charge of consuming the predictions of the machine learning model and doing the actual business logic with it
+
+## Difference between an app and a Kedro pipeline
+
+Note that the previously defined "apps" are not pipelines in the Kedro sense. On the contrary, each app likely contain several (Kedro?) pipelines.  
+
+The main differences between these apps are:
+
+- Each app development / deployment  is likely under the responsibility of different people / teams.
+- Each app has a different development lifecyle. It implies that development can be parallelized, and releasing one app to fix a bug does not imply to release the other ones. If your training pipeline is time /resources consuming, you do not want a bugfix in the *user_app* to trigger a retraining of your model, do you?
+- Each app has its own orchestration timeline. For instance, the data produced by the etl can be stored independently of whether the *user_app* and the *ml_app* consume them "on the fly" or not.
+- Each app do not communicate with the other apart from a clear interface: the data schema accepted as inputs/ output of each app.
+
+## Apps development lifecycle in a machine learning project
+
+### The data scientist creates at least part of the 3 apps
+
+Note that there are **as many _etl_app_ and _user_app_** as needed for the different use of your model. Since **training the model is a specific use, the data scientist will need one to create its own _etl_app_ and _user_app_**. These apps will very likely be replaced later by the true business app dedicated to the model use.
+
+We saw that the data scientist has to create some code that will be replaced by other people code when deploying the model. As a consequence, the interactions between these apps must be very clearly defined at the beginning of the project. We claim that it is possible to cover most use case with the following schema:
+
+![apps_interaction](../imgs/apps_interaction.png)
+
+The *ml_app* takes `instances` (i.e. examples of the business object to handle) as input. This implies that the *ml_app* will include some machine learning-specific preprocessing and not only the model training. It also (optionally) takes labels as inputs if the underlying problem is supervised. Even in this situation, the labels will not be known at inference time so the *etl_app* does not necessarily produce them.
+
+This is a key principle: anyone who wants to consume the model later will need to bring instances of the same business object.
+
+### The *etl_app*
+
+The *etl_app* is the one in charge of bringing the data to the *ml_app*. As a consequence, each different *user_app* will likely have to develop its associated *etl_app* to consume the *ml_app*.
+
+From the data scientist point of view, this app will create the training dataset. This app can do very different things:
+
+- send request over an API
+- extract from a database (with SQL, SAS...)
+- scrape data from a website
+- download data from an URL
+- read data from disk
+- ...
+
+For the labels, in addition of above possibility, this app can be a **labelling tool** with human labellers who provide the needed "true reference" as labels.
+
+It is also common to mix several of above approaches to gather different data sources, and to have different Kedro pipelines in this app.
+
+Note that during a training, this app very likely retrieves batch data from a given time period. This will necessarily be different when using the model, because the user often want to use live stream data.
+
+### The *ml_app*
+
+This app is the core of the data scientist work. It is at least composed of two kedro pipelines:
+
+- a *training* pipeline, which produces all the artifacts (e.g. any object fitted on data, including obviously the machine learning model itself)
+- an *inference* pipeline which takes an instance as input and returns the prediction of the model
+
+It is quite common to have other pipelines depending on the data scientist needs (an *evaluation* pipelines which produces metrics for a given model, an *explanation* pipeline to produce explanation for a specific instance like shap values or importance pixel, ...).
+
+It is quite common to see data scientists duplicate the code when creating the inference pipeline, because it is written after the training pipeline. **Thanks to kedro tags, it is possible to mark a node to use it in two different pipelines**. Reuse is a key component to improve quality and deployment speed. **Each time a node is created (i.e. a function is called), the data scientist should wonder if it will be used in *training* pipeline only or in both (*training* and *inference*), and tag it accordingly.**
+
+### The *user_app*
+
+The *user_app* must not be aware of how the inference pipeline operates under the hood. The *user_app* must either:
+
+- takes a *run_id* from mlflow to retrieve the model from mlflow and predict with it. This is mainly useful for batch predictions.
+- call the served model from an API endpoint and only get predictions as inputs. This assumes that the model has been served, which is very easy with mlflow.
+
+After that, the *user_app* can use the predictions and apply any needed business logic to them.
diff --git a/docs/source/05_framework_ml/03_framework_solutions.md b/docs/source/05_framework_ml/03_framework_solutions.md
new file mode 100644
index 00000000..6f41136a
--- /dev/null
+++ b/docs/source/05_framework_ml/03_framework_solutions.md
@@ -0,0 +1,115 @@
+# ``kedro-mlflow`` mlops solution
+
+## Reminder
+
+We assume that we want to solve the following challenges among those described in ["Why we need a mlops framework"](docs\source\05_framework_ml\01_why_framework.md) section:
+
+- serve pipelines (which handles business objects) instead of models
+- synchronize training and inference by packaging inference pipeline at training time
+
+## Enforcing these principles with a dedicated tool
+
+### Synchronizing training and inference pipeline
+
+To solve the problem of desynchronization between training and inference, ``kedro-mlflow`` offers a `PipelineML` class (which subclasses Kedro `Pipeline` class). A `PipelineML` is simply a Kedro standard ``Pipeline`` (the "training") which has a reference to another ``Pipeline`` (the "inference"). The two pipelines must share a common input DataSet name, which represents the data you will perform operations on (either train on for the training pipeline, or predict on for the inference pipeline).
+
+This class implements several methods to compare the ``DataCatalog``s associated to each of the two binded pipelines and performs subsetting oparations. This makes it quite difficult to handle directly. Fortunately, ``kedro-mlflow`` provides a convenient API to create ``PipelineML`` objects: the ``pipeline_ml_factory`` function.
+
+The use of ``pipeline_ml_factory`` is very straightforward, especially if you have used the [project architecture described previously](docs\source\05_framework_ml\02_ml_project_components.md). The best place to create such an object is your `hooks.py` file which will look like this:
+
+```python
+# hooks.py
+from kedro_mlflow_tutorial.pipelines.ml_app.pipeline import create_ml_pipeline
+
+class ProjectHooks:
+    @hook_impl
+    def register_pipelines(self) -> Dict[str, Pipeline]:
+
+        ml_pipeline = create_ml_pipeline()
+
+        # convert your two pipelines to a PipelinML object
+        training_pipeline_ml = pipeline_ml_factory(
+            training=ml_pipeline.only_nodes_with_tags("training"),
+            inference=ml_pipeline.only_nodes_with_tags("inference"),
+            input_name="instances"
+        )
+
+        return {
+            "__default__": training_pipeline_ml
+        }
+
+```
+
+> So, what? We have created a link between our two pipelines, but the gain is not obvious at first glance. The 2 following sections demonstrates that such a construction enables to package and serve automatically the inference pipeline when executing the training one.
+
+### Packaging and serving a Kedro Pipeline
+
+Mlflow offers the possibility to create [custom model class](https://www.mlflow.org/docs/latest/models.html#custom-python-models). Mlflow offers a variety of tool to package/containerize, deploy and serve such models.
+
+``kedro-mlflow`` has a ``KedroPipelineModel`` class (which inherits from ``mlflow.pyfunc.PythonModel``) which can turn any kedro ``PipelineML`` object to a Mlflow Model.
+
+To convert a ``PipelineML``, you need to declare it as a ``KedroPipelineModel`` and then log it to mlflow:
+
+```python
+from pathlib import Path
+from kedro.framework.context import load_context
+from kedro_mlflow.mlflow import KedroPipelineModel
+from mlflow.models import ModelSignature
+
+# pipeline_training is your PipelineML object, created as previsously
+catalog = load_context(".").io
+
+# artifacts are all the inputs of the inference pipelines that are persisted in the catalog
+artifacts = pipeline_training.extract_pipeline_artifacts(catalog)
+
+# (optiona) get the schema of the input dataset
+input_data = catalog.load(pipeline_training.input_name)
+model_signature = infer_signature(model_input=input_data)
+
+kedro_model = KedroPipelineModel(
+    pipeline_ml=pipeline_training,
+    catalog=catalog
+)
+
+mlflow.pyfunc.log_model(
+    artifact_path="model",
+    python_model=kedro_model,
+    artifacts=artifacts,
+    conda_env={"python": "3.7.0"},
+    model_signature=model_signature
+)
+```
+
+Note that you need to provide the ``log_model`` function a bunch of non trivial-to-retrieve informations (the conda environment, the "artifacts" i.e. the persisted data you need to reuse like tokenizers / ml models / encoders, the model signature i.e. the columns names and types...). The ``PipelineML`` object has methods like `extract_pipeline_artifacts` to help you, but it needs some work on your side.
+
+> Saving Kedro pipelines as Mlflow Model objects is convenient and enable pipeline serving serving. However, it does not does not solve the decorrelation between training and inference: each time one triggers a training pipeline, (s)he must think to save it immediately afterwards. Good news: triggering operations at some "execution moment" of a Kedro ``Pipeline`` (like after it finished runnning) is exactly what hooks are designed for!
+
+### kedro-mlflow's magic: inference autologging
+
+When running the training pipeline, we have all the desired informations we want to pass to the ``KedroPipelineModel`` class and ``mlflow.pyfunc.log_model`` function:
+
+- the artifacts exist in the DataCatalog if they are persisted
+- the "instances" dataset is loaded at the beginning of training, thus we can infer its schema (columns names and types)
+- the inference and training pipeline codes are retrieved at the same moments, so consistency checks can be performed
+
+Hence, ``kedro-mlflow`` provides a ``MlflowPipelineHook.after_pipeline_run`` hook which perfoms the following operations:
+
+- check if the pipeline that have ust been run is a ``PipelineML`` object
+- in case it is, create the ``KedroPipelineModel`` like above and log it to mlflow
+
+> We have achieved perfect synchronicity since the exact inference pipeline (with code, and artifacts) will be logged in mlflow each time the training pipeline is executed. The model is than accessible in the mlflow UI "artifacts" section and can be downloaded, or [served as an API with the ``mlflow serve`` command](https://www.mlflow.org/docs/latest/cli.html#mlflow-models-serve), or [it can be used in the `catalog.yml` with the `MlflowModelLogger` for further reuse](https://github.com/Galileo-Galilei/kedro-mlflow-tutorial#serve-the-inference-pipeline-to-a-end-user).
+
+### Reuse the model in kedro
+
+Say that you an to reuse this inference model as the input of another kedro pipeline (one of the "user_app" application). ``kedro-mlflow`` provides a ``MlflowModelLoggerDataSet`` class which can be used int the ``catalog.yml`` file:
+
+```yaml
+# catalog.yml
+
+pipeline_inference_model:
+  type: kedro_mlflow.io.models.MlflowModelLoggerDataSet
+  flavor: mlflow.pyfunc
+  pyfunc_workflow: python_model
+  artifact_path: kedro_mlflow_tutorial  # the name of your mlflow folder = the model_name in pipeline_ml_factory
+  run_id: <your-run-id>  
+```
diff --git a/docs/source/05_framework_ml/04_example_project.md b/docs/source/05_framework_ml/04_example_project.md
new file mode 100644
index 00000000..7a518348
--- /dev/null
+++ b/docs/source/05_framework_ml/04_example_project.md
@@ -0,0 +1,75 @@
+# Project example
+
+## 5 mn summary
+
+If you don't want to read the entire explanations, here is a summary:
+
+1. install ``kedro-mlflow`` ``MlflowPipelineHook`` (this is done automatically if you have installed ``kedro-mlflow`` in a ``kedro>=0.16.5`` project)
+2. Turn your training pipeline in a ``PipelineML`` object  with ``pipeline_ml_factory`` function in your ``hooks.py``:
+
+    ```python
+    # hooks.py
+    from kedro_mlflow_tutorial.pipelines.ml_app.pipeline import create_ml_pipeline
+
+    ...
+
+    class ProjectHooks:
+        @hook_impl
+        def register_pipelines(self) -> Dict[str, Pipeline]:
+
+            ...
+
+            ml_pipeline = create_ml_pipeline()
+            training_pipeline_ml = pipeline_ml_factory(
+                training=ml_pipeline.only_nodes_with_tags("training"),
+                inference=ml_pipeline.only_nodes_with_tags("inference"),
+                input_name="instances",
+                model_name="kedro_mlflow_tutorial",
+                conda_env={
+                    "python": 3.7,
+                    "pip": [f"kedro_mlflow_tutorial=={PROJECT_VERSION}"],
+                },
+                model_signature="auto",
+            )
+
+            ...
+
+            return {
+                "training": training_pipeline_ml,
+                ...
+            }
+    ```
+
+3. persist your artifacts locally in the ``catalog.yml``
+
+    ```yaml
+    label_encoder:
+    type: pickle.PickleDataSet  # <- This must be any Kedro Dataset other than "MemoryDataSet"
+    filepath: data/06_models/label_encoder.pkl  # <- This must be a local path, no matter what is your mlflow storage (S3 or other)
+    ```
+
+4. Launch your training pipeline:
+
+    ```bash
+    kedro run --pipeline=training
+    ```
+
+    **The inference pipeline will _automagically_ be logged as a mlflo model at the end!**
+
+5. Go to the UI, retrieve the run id of your "inference pipeline" model and use it as you want, e.g. in the catalog.yml:
+
+    ```yaml
+    # catalog.yml
+
+    pipeline_inference_model:
+    type: kedro_mlflow.io.models.MlflowModelLoggerDataSet
+    flavor: mlflow.pyfunc
+    pyfunc_workflow: python_model
+    artifact_path: kedro_mlflow_tutorial  # the name of your mlflow folder = the model_name in pipeline_ml_factory
+    run_id: <your-run-id>  
+    ```
+
+
+## Complete step by step demo project with code
+
+A step by step tutorial with code is available in the [kedro-mlflow-tutorial repository on github](https://github.com/Galileo-Galilei/kedro-mlflow-tutorial#serve-the-inference-pipeline-to-a-end-user).
diff --git a/docs/source/05_framework_ml/05_example_project_step_by_step.md b/docs/source/05_framework_ml/05_example_project_step_by_step.md
new file mode 100644
index 00000000..cb896692
--- /dev/null
+++ b/docs/source/05_framework_ml/05_example_project_step_by_step.md
@@ -0,0 +1,202 @@
+# Framework example
+
+## Introduction
+
+### Pre-requisite
+
+This assumes the user is familiar with Kedro. We will refer to a `kedro>=0.16.5, <0.17.0` project template files (e.g. `catalog.yml`, `pipeline.py` and `hooks.py`).
+
+### Disclaimer
+
+**This is NOT a Kaggle competition. I will not try to create the best model (nor even a _good_ model)** to solve this problem. The goal of this tutorial is to demonstrate how the `pipeline_ml_factory` can help to maintain consistency between training and inference and prepare deployment.
+
+The associated code can be found on github, under [kedro-mlflow-tutorial](https://github.com/Galileo-Galilei/kedro-mlflow-tutorial) repo.
+
+### Introducing the task and dataset
+
+We will use the [IMDB movie review](https://www.kaggle.com/lakshmi25npathi/imdb-dataset-of-50k-movie-reviews) dataset as an example. This dataset contains 50k movie reviews with associated "positive" or "negative" value manually labelled by a human.
+
+We will train a classifier for binary classification to predict the sentiment associated to a movie review.
+
+You can find [many notebooks on Kaggle](https://www.kaggle.com/lakshmi25npathi/imdb-dataset-of-50k-movie-reviews/notebooks) to learn more about this dataset.
+
+## Building the project
+
+To understand the division in *etl_app*, *ml_app* and *user_app*, you can refer to ["The components of a machine learning application"](./02_ml_project_components.md).
+
+### The etl_app
+
+We create a very basic `etl_app` for this example. It can be really complex in real life. What matters is that it is composed of two pipelines which produce `instances` and `labels` datasets.
+
+The DAG looks like this:
+
+![etl_app](../imgs/etl_app.png)
+
+Where the "huggingface_split" parameter is just here to define if we should produce "training" or "test" data. We persist the `instances` and `labels` in the DataCatalog for further reuse:
+
+```yaml
+# catalog.yml
+#### ETL
+instances:
+  type: pickle.PickleDataSet
+  filepath: data/01_raw/instances_${huggingface_split}.pkl
+
+labels:
+  type: pickle.PickleDataSet
+  filepath: data/01_raw/labels_${huggingface_split}.pkl
+```
+
+
+### The ml_app
+
+Once the instances and labels have been created with the *etl_app*, we can create and launch the machine learning training and inference pipelines. The inference pipeline is the one you will deploy for your end users.
+
+#### A vanilla example: training a machine learning model
+
+The most basic project we can imagine is the following: the business object "instances" is a numeric matrix, and we can directely train a model on it.
+
+The entire pipeline is:
+
+```python
+# pipeline.py
+def create_ml_pipeline_vanilla(**kwargs):
+    """This pipeline is just an example to generate a kedro viz.
+    It is NOT intended to be used directly.
+    """
+
+    return Pipeline(
+        [
+            node(
+                func=train_model,
+                inputs=dict(
+                    data="instances", hyperparameters="params:xgb_hyperparameters",
+                ),
+                outputs="model",
+                tags=["training"],
+            ),
+            node(
+                func=predict_with_model,
+                inputs=dict(model="model", data="instances"),
+                outputs="predictions",
+                tags=["inference"],
+            ),
+        ]
+    )
+```
+
+with the associated graph:
+
+![ml_pipeline_vanilla](../imgs/ml_pipeline_vanilla.png)
+
+You can **filter the pipeline to separate it between training and inference with efficient node reuse**.
+
+| `pipeline.only_nodes_with_tags("training")`      | `pipeline.only_nodes_with_tags("inference")` |
+| ----------- | ----------- |
+| ![ml_pipeline_vanilla_training](../imgs/ml_pipeline/vanilla/training.png)| ![ml_pipeline_vanilla_inference](../imgs/ml_pipeline/vanilla/inference.png)|
+
+Running the `training` pipeline will create the model, and if it is persisted in the `catalog.yml` file, you will be able to run the ``inference`` pipeline:
+
+```yaml
+# catalog.yml
+model:
+  type: pickle.PickleDataSet # anything other than a MemoryDataSet
+  filepath: data/06_models/model.pkl # a local filepath
+```
+
+To make ``kedro-mlflow`` log your inference pipeline automatically after each `kedro run --pipeline=training` command, declare your pipeline as a ``PipelineML`` object in your ``hooks.py``:
+
+```python
+# hooks.py
+class ProjectHooks:
+    @hook_impl
+    def register_pipelines(self) -> Dict[str, Pipeline]:
+        pipeline_training = pipeline_ml_factory(
+            training=ml_pipeline.only_nodes_with_tags("training"),
+            inference=ml_pipeline.only_nodes_with_tags("inference"),
+            input_name="instances",
+        )
+        return {
+            "training": pipeline_training,
+            "__default__": pipeline_training
+        }
+```
+
+> This example is nice, but not realistic. It is extremely rare to train a model directly on raw data. It would be convenient to be able to add a preprocessing node, wouldn't it ? This is the purpose of the next session.
+
+#### Sharing preprocessing: text cleaning example
+
+Assume you want to add an extra preprocessing node before training the model. The previous entire DAG would look like this:
+
+![ml_pipeline_with_preprocessing](../imgs/ml_pipeline/preprocessing/all.png)|
+
+
+You can once again filter this huge pipeline to separate it between `training` and `inference`. **Since you do not duplicate nodes, this ensure consistency because you are absolutely sure you call the exact same functions** in the two pipelines.
+
+| `pipeline.only_nodes_with_tags("training")`      | `pipeline.only_nodes_with_tags("inference")` |
+| ----------- | ----------- |
+| ![ml_pipeline_preprocessing_training](../imgs/ml_pipeline/preprocessing/training.png)| ![ml_pipeline_preprocessing_inference](../imgs/ml_pipeline/preprocessing/inference.png)|
+
+You want to serve the entire inference pipeline (extra preprocessing included) to your end users, and this is exactly what we are going to do here!
+
+**You do not need to update your ``hooks.py`` file: the declaration made in previous paragraph will log your entire inference pipeline (preprocessing included) after each ``kedro run --pipeline=training`` command.**
+
+> Wait! In real life, it is often more complex: preprocessing is often not declared manually through a function but also fitted on data. For instance, you can fit an imputer, a tokenizer, an encoder... and reuse them in the inference pipeline. We need a way to persist these objects.
+
+#### Reusing fitted objects: the example of a tokenizer
+
+Before sending data to the model, we need to convert it in a numerical format. For this example, we assume that we use the keras tokenizer, and its default method ``texts_to_matrix`` which encodes a list of tokens to a matrix where each columns represents a word in a fixed-sized vocabulary. This methods is known as "bag of words", and is a simple yet quite efficient way to convert text to numeric (but it lost relative position of words in a sentence, and much better way exist for such conversion, like embeddings).
+
+We add 2 nodes in our ml pipeline:
+
+- a ``fit_tokenizer`` node, which will be used only during training. The node has "training" tag.
+- a ``tokenize_text`` node, which will be used both in training and inference. The node has ["training", "inference"] tags.
+
+| `pipeline.only_nodes_with_tags("training")`      | `pipeline.only_nodes_with_tags("inference")` |
+| ----------- | ----------- |
+| ![ml_pipeline_tokenizer_training](../imgs/ml_pipeline/tokenizer/training.png)| ![ml_pipeline_tokenizer_inference](../imgs/ml_pipeline/tokenizer/inference.png)|
+
+In order to enable ``kedro-mlflow`` to log these stopwords as artifacts and reuse them during inference, we need to persist the locally:
+
+```yaml
+# catalog.yml
+tokenizer:
+  type: pickle.PickleDataSet # anything other than a MemoryDataSet
+  filepath: data/06_models/tokenizer.pkl # a local filepath
+```
+
+**Since our ``training`` pipeline is a `PipelineML` object created by `pipeline_ml_factory` function, `kedro-mlflow` will automatically resolve the dependencies between ``training`` and ``inference`` pipelines and will log the tokenizer as an artifact of our model.** It will be then automatically reused when predicting with the model. Nice, isn't it?
+
+> Wait! This is great, but we sometimes also need to reuse some parameters or shared inputs between my inference pipeline. For instance, my preprocessing function has a list of stopwords, and I need to use the same stopwords during inference.
+
+#### Reusing shared inputs: the example of stopwords
+
+Let's add an extra node during our preprocessing with ``["training", "inference"]`` tags which removes stopwords.
+
+| `pipeline.only_nodes_with_tags("training")`      | `pipeline.only_nodes_with_tags("inference")` |
+| ----------- | ----------- |
+| ![ml_pipeline_shared_inputs_training](../imgs/ml_pipeline/shared_inputs/training.png)| ![ml_pipeline_shared_inputs_inference](../imgs/ml_pipeline/shared_inputs/inference.png)|
+
+Like every reusable artifacts, we need to persist these stopwords to make ``kedro-mlflow`` able to reuse them later:
+
+```yaml
+# catalog.yml
+en_stopwords:
+  type: yaml.YAMLDataSet # anything other than a MemoryDataSet
+  filepath: data/01_raw/stopwords.yml # a local filepath
+```
+
+> This is nice! We are able to create a complex inference pipeline which is always synchronized with the training pipeline. However, we sometimes need to add some extra operations to validate / evaluate the model during training, and these operations are not related to inference. Is it possible? Sure!
+
+#### Adding some metrics and monitoring
+
+Say that before training your model, you to split it between train and test (e.g. to early stop on test), and then calculate some metrics on this test data (note that it will never replace using a real validation dataset to estimate the real performance of your model).
+
+You can add some nodes which are completely unrelated to your ``inference`` pipeline in your ``training`` pipeline.
+
+#### Labels post processing
+
+#### Final result:
+
+### The user_app
+
+Use `MlflowModelLoggerDataset` to force reading from a given run_id.
diff --git a/docs/source/05_framework_ml/index.rst b/docs/source/05_framework_ml/index.rst
new file mode 100644
index 00000000..1b8a42c5
--- /dev/null
+++ b/docs/source/05_framework_ml/index.rst
@@ -0,0 +1,10 @@
+Introduction
+============
+
+.. toctree::
+   :maxdepth: 4
+
+   Why we need a mlops framework for development lifecycle <01_why_framework.md>
+   The architecture of a machine learning project  <02_ml_project_components.md>
+   An efficient tool for model serving and training / inference synchronization <03_framework_solutions.md>
+   A step by step example <04_example_project.md>
diff --git a/docs/source/07_python_objects/03_Pipelines.md b/docs/source/07_python_objects/03_Pipelines.md
index de8fb561..44cfc064 100644
--- a/docs/source/07_python_objects/03_Pipelines.md
+++ b/docs/source/07_python_objects/03_Pipelines.md
@@ -64,3 +64,15 @@ mlflow.pyfunc.log_model(
     model_signature=model_signature
 )
 ```
+
+It is also possible to pass arguments to `KedroPipelineModel` to specify the runner or the copy_mode of MemoryDataSet for the inference Pipeline. This may be faster especially for  compiled model (e.g keras, tensorflow), and more suitable for an API serving pattern.
+
+```python
+KedroPipelineModel(
+            pipeline_ml=pipeline_training,
+            catalog=catalog,
+            copy_mode="assign"
+        )
+```
+
+Available `copy_mode` are "assign", "copy" and "deepcopy". It is possible to pass a dictionary to specify different copy mode fo each dataset.
diff --git a/docs/source/07_python_objects/04_CLI.md b/docs/source/07_python_objects/04_CLI.md
index f344fb49..4f852075 100644
--- a/docs/source/07_python_objects/04_CLI.md
+++ b/docs/source/07_python_objects/04_CLI.md
@@ -3,9 +3,13 @@
 ## ``init``
 
  ``kedro mlflow init``: this command is needed to initalize your project. You cannot run any other commands before you run this one once. It performs 2 actions:
-    - creates a ``mlflow.yml`` configuration file in your ``conf/base`` folder
+    - creates a ``mlflow.yml`` configuration file in your ``conf/local`` folder
     - replace the ``src/PYTHON_PACKAGE/run.py`` file by an updated version of the template. If your template has been modified since project creation, a warning wil be raised. You can either run ``kedro mlflow init --force`` to ignore this warning (but this will erase your ``run.py``) or [set hooks manually](#new-hooks).
 
+Init has two arguments:
+- `--env` which enable to specifiy another environment where the mlflow.yml should be created (e.g, `base`)
+- `--force` which overrides the `mlflow.yml` if it already exists and replaces it with the default one. Use it with caution!
+
 ## ``ui``
 
 ``kedro mlflow ui``: this command opens the mlflow UI (basically launches the ``mlflow ui`` command with the configuration of your ``mlflow.yml`` file)
diff --git a/docs/source/imgs/apps_interaction.png b/docs/source/imgs/apps_interaction.png
new file mode 100644
index 00000000..6cab4a8f
Binary files /dev/null and b/docs/source/imgs/apps_interaction.png differ
diff --git a/docs/source/imgs/etl_app.png b/docs/source/imgs/etl_app.png
new file mode 100644
index 00000000..26b65600
Binary files /dev/null and b/docs/source/imgs/etl_app.png differ
diff --git a/docs/source/imgs/ml_pipeline/preprocessing/all.PNG b/docs/source/imgs/ml_pipeline/preprocessing/all.PNG
new file mode 100644
index 00000000..339bc79c
Binary files /dev/null and b/docs/source/imgs/ml_pipeline/preprocessing/all.PNG differ
diff --git a/docs/source/imgs/ml_pipeline/preprocessing/inference.PNG b/docs/source/imgs/ml_pipeline/preprocessing/inference.PNG
new file mode 100644
index 00000000..2ab791ba
Binary files /dev/null and b/docs/source/imgs/ml_pipeline/preprocessing/inference.PNG differ
diff --git a/docs/source/imgs/ml_pipeline/preprocessing/training.PNG b/docs/source/imgs/ml_pipeline/preprocessing/training.PNG
new file mode 100644
index 00000000..a94693c0
Binary files /dev/null and b/docs/source/imgs/ml_pipeline/preprocessing/training.PNG differ
diff --git a/docs/source/imgs/ml_pipeline/shared_inputs/all.PNG b/docs/source/imgs/ml_pipeline/shared_inputs/all.PNG
new file mode 100644
index 00000000..dc89551b
Binary files /dev/null and b/docs/source/imgs/ml_pipeline/shared_inputs/all.PNG differ
diff --git a/docs/source/imgs/ml_pipeline/shared_inputs/inference.PNG b/docs/source/imgs/ml_pipeline/shared_inputs/inference.PNG
new file mode 100644
index 00000000..5a41a767
Binary files /dev/null and b/docs/source/imgs/ml_pipeline/shared_inputs/inference.PNG differ
diff --git a/docs/source/imgs/ml_pipeline/shared_inputs/training.PNG b/docs/source/imgs/ml_pipeline/shared_inputs/training.PNG
new file mode 100644
index 00000000..90f6719f
Binary files /dev/null and b/docs/source/imgs/ml_pipeline/shared_inputs/training.PNG differ
diff --git a/docs/source/imgs/ml_pipeline/tokenizer/all.PNG b/docs/source/imgs/ml_pipeline/tokenizer/all.PNG
new file mode 100644
index 00000000..332717c9
Binary files /dev/null and b/docs/source/imgs/ml_pipeline/tokenizer/all.PNG differ
diff --git a/docs/source/imgs/ml_pipeline/tokenizer/inference.PNG b/docs/source/imgs/ml_pipeline/tokenizer/inference.PNG
new file mode 100644
index 00000000..29d511dc
Binary files /dev/null and b/docs/source/imgs/ml_pipeline/tokenizer/inference.PNG differ
diff --git a/docs/source/imgs/ml_pipeline/tokenizer/training.PNG b/docs/source/imgs/ml_pipeline/tokenizer/training.PNG
new file mode 100644
index 00000000..330acb31
Binary files /dev/null and b/docs/source/imgs/ml_pipeline/tokenizer/training.PNG differ
diff --git a/docs/source/imgs/ml_pipeline/vanilla/all.PNG b/docs/source/imgs/ml_pipeline/vanilla/all.PNG
new file mode 100644
index 00000000..fb4f4a1f
Binary files /dev/null and b/docs/source/imgs/ml_pipeline/vanilla/all.PNG differ
diff --git a/docs/source/imgs/ml_pipeline/vanilla/inference.PNG b/docs/source/imgs/ml_pipeline/vanilla/inference.PNG
new file mode 100644
index 00000000..8249d79f
Binary files /dev/null and b/docs/source/imgs/ml_pipeline/vanilla/inference.PNG differ
diff --git a/docs/source/imgs/ml_pipeline/vanilla/training.PNG b/docs/source/imgs/ml_pipeline/vanilla/training.PNG
new file mode 100644
index 00000000..2b571502
Binary files /dev/null and b/docs/source/imgs/ml_pipeline/vanilla/training.PNG differ
diff --git a/kedro_mlflow/__init__.py b/kedro_mlflow/__init__.py
index a5e40e55..c98e36db 100644
--- a/kedro_mlflow/__init__.py
+++ b/kedro_mlflow/__init__.py
@@ -1,3 +1,3 @@
 """kedro-mlflow plugin constants
 """
-__version__ = "0.4.1"
+__version__ = "0.5.0"
diff --git a/kedro_mlflow/framework/cli/cli.py b/kedro_mlflow/framework/cli/cli.py
index 4c4b5418..181097b1 100644
--- a/kedro_mlflow/framework/cli/cli.py
+++ b/kedro_mlflow/framework/cli/cli.py
@@ -6,7 +6,7 @@
 
 from kedro_mlflow.framework.cli.cli_utils import write_jinja_template
 from kedro_mlflow.framework.context import get_mlflow_config
-from kedro_mlflow.utils import _already_updated, _is_kedro_project
+from kedro_mlflow.utils import _is_kedro_project
 
 try:
     from kedro.framework.context import get_static_project_data
@@ -26,20 +26,14 @@ def reset_commands(self):
         # add commands on the fly based on conditions
         if _is_kedro_project():
             self.add_command(init)
-            if _already_updated():
-                self.add_command(ui)
-                # self.add_command(run) # TODO : IMPLEMENT THIS FUNCTION
+            self.add_command(ui)
+            # self.add_command(run) # TODO : IMPLEMENT THIS FUNCTION
         else:
             self.add_command(new)
 
     def list_commands(self, ctx):
         self.reset_commands()
         commands_list = sorted(self.commands)
-        if commands_list == ["init"]:
-            click.secho(
-                """\n\nYou have not updated your template yet.\nThis is mandatory to use 'kedro-mlflow' plugin.\nPlease run the following command before you can access to other commands :\n\n$ kedro mlflow init""",
-                fg="yellow",
-            )
         return commands_list
 
     def get_command(self, ctx, cmd_name):
@@ -56,16 +50,22 @@ def commands():
 @commands.command(name="mlflow", cls=KedroClickGroup)
 def mlflow_commands():
     """Use mlflow-specific commands inside kedro project."""
-    pass
+    pass  # pragma: no cover
 
 
 @mlflow_commands.command()
+@click.option(
+    "--env",
+    "-e",
+    default="local",
+    help="The name of the kedro environment where the 'mlflow.yml' should be created. Default to 'local'",
+)
 @click.option(
     "--force",
     "-f",
     is_flag=True,
     default=False,
-    help="Update the template without any checks. The modifications you made in 'run.py' will be lost.",
+    help="Update the template without any checks. If a 'mlflow.yml' already eists, it will be overridden.",
 )
 @click.option(
     "--silent",
@@ -74,49 +74,56 @@ def mlflow_commands():
     default=False,
     help="Should message be logged when files are modified?",
 )
-def init(force, silent):
+def init(env, force, silent):
     """Updates the template of a kedro project.
-    Running this command is mandatory to use kedro-mlflow.
-    2 actions are performed :
-        1. Add "conf/base/mlflow.yml": This is a configuration file
-         used for run parametrization when calling "kedro run" command.
-         See INSERT_DOC_URL for further details.
-        2. Modify "src/YOUR_PACKAGE_NAME/run.py" to add mlflow hooks
-         to the ProjectContext. This will erase your current "run.py"
-         script and all your modifications will be lost.
-         If you do not want to erase "run.py", insert the hooks manually
+    Running this command is mandatory to use kedro-mlflow. This
+    adds a configuration file used for run parametrization when
+    calling "kedro run" command.
     """
 
     # get constants
+    mlflow_yml = "mlflow.yml"
     project_path = Path().cwd()
     project_globals = get_static_project_data(project_path)
     context = load_context(project_path)
-    conf_root = context.CONF_ROOT
+    mlflow_yml_path = project_path / context.CONF_ROOT / env / mlflow_yml
 
-    # mlflow.yml is just a static file,
-    # but the name of the experiment is set to be the same as the project
-    mlflow_yml = "mlflow.yml"
-    write_jinja_template(
-        src=TEMPLATE_FOLDER_PATH / mlflow_yml,
-        is_cookiecutter=False,
-        dst=project_path / conf_root / "base" / mlflow_yml,
-        python_package=project_globals["package_name"],
-    )
-    if not silent:
+    # mlflow.yml is just a static file
+    if mlflow_yml_path.is_file() and not force:
         click.secho(
             click.style(
-                f"'{conf_root}/base/mlflow.yml' successfully updated.", fg="green"
+                f"A 'mlflow.yml' already exists at '{mlflow_yml_path}' You can use the ``--force`` option to override it.",
+                fg="red",
             )
         )
+        return 1
+    else:
+        try:
+            write_jinja_template(
+                src=TEMPLATE_FOLDER_PATH / mlflow_yml,
+                is_cookiecutter=False,
+                dst=mlflow_yml_path,
+                python_package=project_globals["package_name"],
+            )
+        except FileNotFoundError:
+            click.secho(
+                click.style(
+                    f"No env '{env}' found. Please check this folder exists inside '{context.CONF_ROOT}' folder.",
+                    fg="red",
+                )
+            )
+            return 1
+        if not silent:
+            click.secho(
+                click.style(
+                    f"'{context.CONF_ROOT}/{env}/{mlflow_yml}' successfully updated.",
+                    fg="green",
+                )
+            )
+        return 0
 
 
 @mlflow_commands.command()
-@click.option(
-    "--project-path",
-    "-p",
-    required=False,
-    help="The environment within conf folder we want to retrieve.",
-)
 @click.option(
     "--env",
     "-e",
@@ -124,15 +131,14 @@ def init(force, silent):
     default="local",
     help="The environment within conf folder we want to retrieve.",
 )
-def ui(project_path, env):
+def ui(env):
     """Opens the mlflow user interface with the
     project-specific settings of mlflow.yml. This interface
     enables to browse and compares runs.
 
     """
 
-    if not project_path:
-        project_path = Path().cwd()
+    project_path = Path().cwd()
     context = load_context(project_path=project_path, env=env)
     # the context must contains the self.mlflow attribues with mlflow configuration
     mlflow_conf = get_mlflow_config(context)
diff --git a/kedro_mlflow/framework/context/config.py b/kedro_mlflow/framework/context/config.py
index 1948613d..7198d3c9 100644
--- a/kedro_mlflow/framework/context/config.py
+++ b/kedro_mlflow/framework/context/config.py
@@ -18,7 +18,14 @@ class KedroMlflowConfig:
 
     UI_OPTS = {"port": None, "host": None}
 
-    NODE_HOOK_OPTS = {"flatten_dict_params": False, "recursive": True, "sep": "."}
+    NODE_HOOK_OPTS = {
+        "flatten_dict_params": False,
+        "recursive": True,
+        "sep": ".",
+        "long_parameters_strategy": "fail",
+    }
+
+    AVAILABLE_LONG_PARAMETERS_STRATEGY = ["fail", "truncate", "tag"]
 
     def __init__(
         self,
@@ -134,8 +141,21 @@ def from_dict(self, configuration: Dict[str, str]):
             opts=node_hook_opts, default=self.NODE_HOOK_OPTS
         )
 
+        # this parameter validation should likely be elsewhere
+        # when refactoring KedroMlflowConfig, we shoudl use an object
+        # to validate data with a @property
+        if (
+            self.node_hook_opts["long_parameters_strategy"]
+            not in self.AVAILABLE_LONG_PARAMETERS_STRATEGY
+        ):
+            strategy_list = ", ".join(self.AVAILABLE_LONG_PARAMETERS_STRATEGY)
+            raise KedroMlflowConfigError(
+                f"'long_parameters_strategy' must be one of [{strategy_list}], "
+                f"got '{self.node_hook_opts['long_parameters_strategy']}'"
+            )
+
         # instantiate mlflow objects to interact with the database
-        # the client must not be create dbefore carefully checking the uri,
+        # the client must not be created before carefully checking the uri,
         # otherwise mlflow creates a mlruns folder to the current location
         self.mlflow_client = mlflow.tracking.MlflowClient(
             tracking_uri=self.mlflow_tracking_uri
diff --git a/kedro_mlflow/framework/context/mlflow_context.py b/kedro_mlflow/framework/context/mlflow_context.py
index 8ed91bae..50824afc 100644
--- a/kedro_mlflow/framework/context/mlflow_context.py
+++ b/kedro_mlflow/framework/context/mlflow_context.py
@@ -1,14 +1,22 @@
+from kedro.config import MissingConfigException
 from kedro.framework.context import KedroContext
 
-from kedro_mlflow.framework.context.config import KedroMlflowConfig
+from kedro_mlflow.framework.context.config import (
+    KedroMlflowConfig,
+    KedroMlflowConfigError,
+)
 
 
 # this could be a read-only property in the context
 # with a @property decorator
 # but for consistency with hook system, it is an external function
 def get_mlflow_config(context: KedroContext):
-
-    conf_mlflow_yml = context.config_loader.get("mlflow*", "mlflow*/**")
+    try:
+        conf_mlflow_yml = context.config_loader.get("mlflow*", "mlflow*/**")
+    except MissingConfigException:
+        raise KedroMlflowConfigError(
+            "No 'mlflow.yml' config file found in environment. Use ``kedro mlflow init`` command in CLI to create a default config file."
+        )
     conf_mlflow = KedroMlflowConfig(context.project_path)
     conf_mlflow.from_dict(conf_mlflow_yml)
     return conf_mlflow
diff --git a/kedro_mlflow/framework/hooks/node_hook.py b/kedro_mlflow/framework/hooks/node_hook.py
index ea200951..67ff364f 100644
--- a/kedro_mlflow/framework/hooks/node_hook.py
+++ b/kedro_mlflow/framework/hooks/node_hook.py
@@ -1,4 +1,5 @@
-from typing import Any, Dict
+import logging
+from typing import Any, Dict, Union
 
 import mlflow
 from kedro.framework.context import load_context
@@ -6,6 +7,7 @@
 from kedro.io import DataCatalog
 from kedro.pipeline import Pipeline
 from kedro.pipeline.node import Node
+from mlflow.utils.validation import MAX_PARAM_VAL_LENGTH
 
 from kedro_mlflow.framework.context import get_mlflow_config
 
@@ -16,6 +18,11 @@ def __init__(self):
         self.flatten = False
         self.recursive = True
         self.sep = "."
+        self.long_parameters_strategy = "fail"
+
+    @property
+    def _logger(self) -> logging.Logger:
+        return logging.getLogger(__name__)
 
     @hook_impl
     def before_pipeline_run(
@@ -50,9 +57,13 @@ def before_pipeline_run(
             extra_params=run_params["extra_params"],
         )
         config = get_mlflow_config(self.context)
+
         self.flatten = config.node_hook_opts["flatten_dict_params"]
         self.recursive = config.node_hook_opts["recursive"]
         self.sep = config.node_hook_opts["sep"]
+        self.long_parameters_strategy = config.node_hook_opts[
+            "long_parameters_strategy"
+        ]
 
     @hook_impl
     def before_node_run(
@@ -76,6 +87,7 @@ def before_node_run(
         # only parameters will be logged. Artifacts must be declared manually in the catalog
         params_inputs = {}
         for k, v in inputs.items():
+            # detect parameters automatically based on kedro reserved names
             if k.startswith("params:"):
                 params_inputs[k[7:]] = v
             elif k == "parameters":
@@ -87,9 +99,36 @@ def before_node_run(
                 d=params_inputs, recursive=self.recursive, sep=self.sep
             )
 
-        mlflow.log_params(params_inputs)
-
+        # logging parameters based on defined strategy
+        for k, v in params_inputs.items():
+            self.log_param(k, v)
 
+    def log_param(self, name: str, value: Union[Dict, int, bool, str]) -> None:
+        str_value = str(value)
+        str_value_length = len(str_value)
+        if str_value_length <= MAX_PARAM_VAL_LENGTH:
+            return mlflow.log_param(name, value)
+        else:
+            if self.long_parameters_strategy == "fail":
+                raise ValueError(
+                    f"Parameter '{name}' length is {str_value_length}, "
+                    f"while mlflow forces it to be lower than '{MAX_PARAM_VAL_LENGTH}'. "
+                    "If you want to bypass it, try to change 'long_parameters_strategy' to"
+                    " 'tag' or 'truncate' in the 'mlflow.yml'configuration file."
+                )
+            elif self.long_parameters_strategy == "tag":
+                self._logger.warning(
+                    f"Parameter '{name}' (value length {str_value_length}) is set as a tag."
+                )
+                mlflow.set_tag(name, value)
+            elif self.long_parameters_strategy == "truncate":
+                self._logger.warning(
+                    f"Parameter '{name}' (value length {str_value_length}) is truncated to its {MAX_PARAM_VAL_LENGTH} first characters."
+                )
+                mlflow.log_param(name, str_value[0:MAX_PARAM_VAL_LENGTH])
+
+
+# this hooks instaitation is necessary for auto-registration
 mlflow_node_hook = MlflowNodeHook()
 
 
diff --git a/kedro_mlflow/framework/hooks/pipeline_hook.py b/kedro_mlflow/framework/hooks/pipeline_hook.py
index 43b33a64..ecd97778 100644
--- a/kedro_mlflow/framework/hooks/pipeline_hook.py
+++ b/kedro_mlflow/framework/hooks/pipeline_hook.py
@@ -1,5 +1,6 @@
 import sys
 from pathlib import Path
+from tempfile import TemporaryDirectory
 from typing import Any, Dict, Union
 
 import mlflow
@@ -139,24 +140,31 @@ def after_pipeline_run(
         """
 
         if isinstance(pipeline, PipelineML):
-            pipeline_catalog = pipeline._extract_pipeline_catalog(catalog)
-            artifacts = pipeline.extract_pipeline_artifacts(pipeline_catalog)
+            with TemporaryDirectory() as tmp_dir:
+                # This will be removed at the end of the context manager,
+                # but we need to log in mlflow beforeremoving the folder
+                pipeline_catalog = pipeline._extract_pipeline_catalog(catalog)
+                artifacts = pipeline.extract_pipeline_artifacts(
+                    pipeline_catalog, temp_folder=Path(tmp_dir)
+                )
 
-            if pipeline.model_signature == "auto":
-                input_data = pipeline_catalog.load(pipeline.input_name)
-                model_signature = infer_signature(model_input=input_data)
-            else:
-                model_signature = pipeline.model_signature
+                if pipeline.model_signature == "auto":
+                    input_data = pipeline_catalog.load(pipeline.input_name)
+                    model_signature = infer_signature(model_input=input_data)
+                else:
+                    model_signature = pipeline.model_signature
 
-            mlflow.pyfunc.log_model(
-                artifact_path=pipeline.model_name,
-                python_model=KedroPipelineModel(
-                    pipeline_ml=pipeline, catalog=pipeline_catalog
-                ),
-                artifacts=artifacts,
-                conda_env=_format_conda_env(pipeline.conda_env),
-                signature=model_signature,
-            )
+                mlflow.pyfunc.log_model(
+                    artifact_path=pipeline.model_name,
+                    python_model=KedroPipelineModel(
+                        pipeline_ml=pipeline,
+                        catalog=pipeline_catalog,
+                        **pipeline.kwargs,
+                    ),
+                    artifacts=artifacts,
+                    conda_env=_format_conda_env(pipeline.conda_env),
+                    signature=model_signature,
+                )
         # Close the mlflow active run at the end of the pipeline to avoid interactions with further runs
         mlflow.end_run()
 
diff --git a/kedro_mlflow/mlflow/kedro_pipeline_model.py b/kedro_mlflow/mlflow/kedro_pipeline_model.py
index 234e27d3..f34b3655 100644
--- a/kedro_mlflow/mlflow/kedro_pipeline_model.py
+++ b/kedro_mlflow/mlflow/kedro_pipeline_model.py
@@ -1,21 +1,92 @@
 from copy import deepcopy
 from pathlib import Path
+from typing import Dict, Optional, Union
 
 from kedro.io import DataCatalog, MemoryDataSet
-from kedro.runner import SequentialRunner
+from kedro.runner import AbstractRunner, SequentialRunner
 from mlflow.pyfunc import PythonModel
 
 from kedro_mlflow.pipeline.pipeline_ml import PipelineML
 
 
 class KedroPipelineModel(PythonModel):
-    def __init__(self, pipeline_ml: PipelineML, catalog: DataCatalog):
+    def __init__(
+        self,
+        pipeline_ml: PipelineML,
+        catalog: DataCatalog,
+        runner: Optional[AbstractRunner] = None,
+        copy_mode: Optional[Union[Dict[str, str], str]] = None,
+    ):
+        """[summary]
+
+        Args:
+            pipeline_ml (PipelineML): A PipelineML object to
+            store as a Mlflow Model
+
+            catalog (DataCatalog): The DataCatalog associated
+            to the PipelineMl
+
+            runner (Optional[AbstractRunner], optional): The kedro
+            AbstractRunner to use. Defaults to SequentialRunner if
+            None.
+
+            copy_mode (Optional[Union[Dict[str,str], str]]):
+            The copy_mode of each DataSet of the catalog
+            when reconstructing the DataCatalog in memory.
+            You can pass either:
+                - None to use Kedro default mode for each dataset
+                - a single string ("deepcopy", "copy" and "assign")
+                to apply to all datasets
+                - a dictionnary with (dataset name, copy_mode) key/values
+                pairs. The associated mode must be a valid kedro mode
+                ("deepcopy", "copy" and "assign") for each. Defaults to None.
+        """
 
         self.pipeline_ml = pipeline_ml
         self.initial_catalog = pipeline_ml._extract_pipeline_catalog(catalog)
-        self.loaded_catalog = DataCatalog()
         # we have the guarantee that there is only one output in inference
         self.output_name = list(pipeline_ml.inference.outputs())[0]
+        self.runner = runner or SequentialRunner()
+        self.copy_mode = copy_mode or {}
+
+        # copy mode has been converted because it is a property
+        # TODO: we need to use the runner's default dataset in case of multithreading
+        self.loaded_catalog = DataCatalog(
+            data_sets={
+                name: MemoryDataSet(copy_mode=copy_mode)
+                for name, copy_mode in self.copy_mode.items()
+            }
+        )
+
+    @property
+    def copy_mode(self):
+        return self._copy_mode
+
+    @copy_mode.setter
+    def copy_mode(self, copy_mode):
+
+        if isinstance(copy_mode, str) or copy_mode is None:
+            # if it is a string, we must create manually the dictionary
+            # of all catalog entries with this copy_mode
+            self._copy_mode = {
+                name: copy_mode
+                for name in self.pipeline_ml.inference.data_sets()
+                if name != self.output_name
+            }
+        elif isinstance(copy_mode, dict):
+            # if it is a dict we will retrieve the copy mode when necessary
+            # it does not matter if this dict does not contain all the catalog entries
+            # the others will be returned as None when accessing with dict.get()
+            self._copy_mode = {
+                name: None
+                for name in self.pipeline_ml.inference.data_sets()
+                if name != self.output_name
+            }
+            self._copy_mode.update(copy_mode)
+        else:
+            raise TypeError(
+                f"'copy_mode' must be a 'str' or a 'dict', not '{type(copy_mode)}'"
+            )
 
     def load_context(self, context):
 
@@ -42,23 +113,26 @@ def load_context(self, context):
                 )
             )
 
-        self.loaded_catalog = deepcopy(self.initial_catalog)
+        updated_catalog = deepcopy(self.initial_catalog)
         for name, uri in context.artifacts.items():
-            self.loaded_catalog._data_sets[name]._filepath = Path(uri)
+            updated_catalog._data_sets[name]._filepath = Path(uri)
+            self.loaded_catalog.save(name=name, data=updated_catalog.load(name))
 
     def predict(self, context, model_input):
         # TODO : checkout out how to pass extra args in predict
         # for instance, to enable parallelization
 
-        self.loaded_catalog.add(
-            data_set_name=self.pipeline_ml.input_name,
-            data_set=MemoryDataSet(model_input),
-            replace=True,
+        self.loaded_catalog.save(
+            name=self.pipeline_ml.input_name,
+            data=model_input,
         )
-        runner = SequentialRunner()
-        run_outputs = runner.run(
+
+        run_output = self.runner.run(
             pipeline=self.pipeline_ml.inference, catalog=self.loaded_catalog
         )
-        return run_outputs[
-            self.output_name
-        ]  # unpack the result to avoid messing the json output
+
+        # unpack the result to avoid messing the json
+        # file with the name of the Kedro dataset
+        unpacked_output = run_output[self.output_name]
+
+        return unpacked_output
diff --git a/kedro_mlflow/pipeline/pipeline_ml.py b/kedro_mlflow/pipeline/pipeline_ml.py
index 9f08ddbb..16b09594 100644
--- a/kedro_mlflow/pipeline/pipeline_ml.py
+++ b/kedro_mlflow/pipeline/pipeline_ml.py
@@ -2,6 +2,7 @@
 from pathlib import Path
 from typing import Any, Callable, Dict, Iterable, Optional, Union
 
+from kedro.extras.datasets.pickle import PickleDataSet
 from kedro.io import DataCatalog, MemoryDataSet
 from kedro.pipeline import Pipeline
 from kedro.pipeline.node import Node
@@ -45,6 +46,7 @@ def __init__(
         conda_env: Optional[Union[str, Path, Dict[str, Any]]] = None,
         model_name: Optional[str] = "model",
         model_signature: Union[ModelSignature, str, None] = "auto",
+        **kwargs,
     ):
 
         """Store all necessary information for calling mlflow.log_model in the pipeline.
@@ -86,6 +88,13 @@ def __init__(
                    - If None, no signature is used
                    - if a `ModelSignature` instance, passed
                    to the underlying dataframe
+            kwargs:
+                extra arguments to be passed to `KedroPipelineModel`
+                when the PipelineML object is automatically saved at the end of a run.
+                This includes:
+                    - `copy_mode`: the copy_mode to be used for underlying dataset
+                    when loaded in memory
+                    - `runner`: the kedro runner to run the model with
         """
 
         super().__init__(nodes, *args, tags=tags)
@@ -95,7 +104,7 @@ def __init__(
         self.model_name = model_name
         self.input_name = input_name
         self.model_signature = model_signature
-
+        self.kwargs = kwargs  # its purpose is to be eventually reused when saving the model within a hook
         self._check_consistency()
 
     @property
@@ -163,6 +172,7 @@ def _check_inference(self, inference: Pipeline) -> None:
     def _extract_pipeline_catalog(self, catalog: DataCatalog) -> DataCatalog:
 
         # check that the pipeline is consistent in case its attributes have been
+        # modified manually
         self._check_consistency()
 
         sub_catalog = DataCatalog()
@@ -175,7 +185,9 @@ def _extract_pipeline_catalog(self, catalog: DataCatalog) -> DataCatalog:
             else:
                 try:
                     data_set = catalog._data_sets[data_set_name]
-                    if isinstance(data_set, MemoryDataSet):
+                    if isinstance(
+                        data_set, MemoryDataSet
+                    ) and not data_set_name.startswith("params:"):
                         raise KedroMlflowPipelineMLDatasetsError(
                             """
                                 The datasets of the training pipeline must be persisted locally
@@ -202,34 +214,55 @@ def _extract_pipeline_catalog(self, catalog: DataCatalog) -> DataCatalog:
 
         return sub_catalog
 
-    def extract_pipeline_artifacts(self, catalog: DataCatalog):
+    def extract_pipeline_artifacts(self, catalog: DataCatalog, temp_folder: Path):
         pipeline_catalog = self._extract_pipeline_catalog(catalog)
-        artifacts = {
-            name: Path(dataset._filepath.as_posix())
-            .resolve()
-            .as_uri()  # weird bug when directly converting PurePosixPath to windows: it is considered as relative
-            for name, dataset in pipeline_catalog._data_sets.items()
-            if name != self.input_name
-        }
+
+        artifacts = {}
+        for name, dataset in pipeline_catalog._data_sets.items():
+            if name != self.input_name:
+                if name.startswith("params:"):
+                    # we need to persist it locally for mlflow access
+                    absolute_param_path = temp_folder / f"params_{name[7:]}.pkl"
+                    persisted_dataset = PickleDataSet(
+                        filepath=absolute_param_path.as_posix()
+                    )
+                    persisted_dataset.save(dataset.load())
+                    artifact_path = absolute_param_path.as_uri()
+                else:
+                    # In this second case, we know it cannot be a MemoryDataSet
+                    # weird bug when directly converting PurePosixPath to windows: it is considered as relative
+                    artifact_path = (
+                        Path(dataset._filepath.as_posix()).resolve().as_uri()
+                    )
+
+                artifacts[name] = artifact_path
+
         return artifacts
 
     def _check_consistency(self) -> None:
+
+        inference_parameters = {
+            input for input in self.inference.inputs() if input.startswith("params:")
+        }
+
         free_inputs_set = (
             self.inference.inputs()
             - {self.input_name}
             - self.all_outputs()
             - self.inputs()
+            - inference_parameters  # it is allowed to pass parameters: they will be automatically persisted by the hook
         )
+
         if len(free_inputs_set) > 0:
             input_set_txt = "\n     - ".join(free_inputs_set)
             raise KedroMlflowPipelineMLInputsError(
                 (
-                    "The following inputs are free for the inference pipeline:"
+                    "The following inputs are free for the inference pipeline:\n"
                     f"    - {input_set_txt}."
-                    " No free input is allowed."
+                    " \nNo free input is allowed."
                     " Please make sure that 'inference.inputs()' are all"
                     " in 'training.all_outputs() + training.inputs()'"
-                    "except 'input_name'."
+                    "except 'input_name' and parameters which starts with 'params:'."
                 )
             )
 
diff --git a/kedro_mlflow/pipeline/pipeline_ml_factory.py b/kedro_mlflow/pipeline/pipeline_ml_factory.py
index 4db7bb4a..aefc1755 100644
--- a/kedro_mlflow/pipeline/pipeline_ml_factory.py
+++ b/kedro_mlflow/pipeline/pipeline_ml_factory.py
@@ -14,6 +14,7 @@ def pipeline_ml_factory(
     conda_env: Optional[Union[str, Path, Dict[str, Any]]] = None,
     model_name: Optional[str] = "model",
     model_signature: Union[ModelSignature, str, None] = "auto",
+    **kwargs
 ) -> PipelineML:
     """This function is a helper to create `PipelineML`
     object directly from two Kedro `Pipelines` (one of
@@ -49,12 +50,19 @@ def pipeline_ml_factory(
             the folder where the model will be stored in
             remote mlflow. Defaults to "model".
         model_signature (Union[ModelSignature, bool]): The mlflow
-             signature of the input dataframe common to training
-             and inference.
-                   - If 'auto', it is infered automatically
-                   - If None, no signature is used
-                   - if a `ModelSignature` instance, passed
-                   to the underlying dataframe
+            signature of the input dataframe common to training
+            and inference.
+                - If 'auto', it is infered automatically
+                - If None, no signature is used
+                - if a `ModelSignature` instance, passed
+                to the underlying dataframe
+        kwargs:
+            extra arguments to be passed to `KedroPipelineModel`
+            when the PipelineML object is automatically saved at the end of a run.
+            This includes:
+                - `copy_mode`: the copy_mode to be used for underlying dataset
+                when loaded in memory
+                - `runner`: the kedro runner to run the model with
 
     Returns:
         PipelineML: A `PipelineML` which is automatically
@@ -70,5 +78,6 @@ def pipeline_ml_factory(
         conda_env=conda_env,
         model_name=model_name,
         model_signature=model_signature,
+        **kwargs
     )
     return pipeline
diff --git a/kedro_mlflow/template/project/mlflow.yml b/kedro_mlflow/template/project/mlflow.yml
index c3fb8e89..d986f603 100644
--- a/kedro_mlflow/template/project/mlflow.yml
+++ b/kedro_mlflow/template/project/mlflow.yml
@@ -41,6 +41,7 @@ hooks:
     flatten_dict_params: False  # if True, parameter which are dictionary will be splitted in multiple parameters when logged in mlflow, one for each key.
     recursive: True  # Should the dictionary flattening be applied recursively (i.e for nested dictionaries)? Not use if `flatten_dict_params` is False.
     sep: "." # In case of recursive flattening, what separator should be used between the keys? E.g. {hyperaparam1: {p1:1, p2:2}}will be logged as hyperaparam1.p1 and hyperaparam1.p2 oin mlflow.
+    long_parameters_strategy: fail # One of ["fail", "tag", "truncate" ] If a parameter is above mlflow limit (currently 250), what should kedro-mlflow do? -> fail, set as a tag instead of a parameter, or truncate it to its 250 first letters?
 
 
 # UI-RELATED PARAMETERS -----------------
diff --git a/kedro_mlflow/utils.py b/kedro_mlflow/utils.py
index 546c3cba..bdff5881 100644
--- a/kedro_mlflow/utils.py
+++ b/kedro_mlflow/utils.py
@@ -1,8 +1,7 @@
 import re
 from pathlib import Path
-from typing import Any, Dict, Union
+from typing import Dict, Union
 
-import anyconfig
 import yaml
 from kedro import __version__ as KEDRO_VERSION
 from kedro.framework.context import load_context
@@ -61,15 +60,6 @@ def _validate_project_path(project_path: Union[str, Path, None] = None) -> Path:
     return project_path
 
 
-def _already_updated(project_path: Union[str, Path, None] = None) -> bool:
-    project_path = _validate_project_path(project_path)
-    flag = False
-    # TODO : add a better check ...
-    if (project_path / "conf" / "base" / "mlflow.yml").is_file():
-        flag = True
-    return flag
-
-
 def _parse_requirements(path, encoding="utf-8"):
     with open(path, mode="r", encoding=encoding) as file_handler:
         requirements = [
diff --git a/mlc_config.json b/mlc_config.json
new file mode 100644
index 00000000..ddf16a8a
--- /dev/null
+++ b/mlc_config.json
@@ -0,0 +1,6 @@
+{
+    "aliveStatusCodes": [
+        429,
+        200
+    ]
+}
diff --git a/pyproject.toml b/pyproject.toml
index 28f4e25c..6d29f28a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,1 +1,13 @@
 [tool.black]
+exclude = "/template/"
+
+[tool.isort]
+multi_line_output=3
+include_trailing_comma=true
+force_grid_wrap=0
+use_parentheses=true
+line_length=88
+ensure_newline_before_comments=true
+sections=["FUTURE","STDLIB","THIRDPARTY","FIRSTPARTY","LOCALFOLDER"]
+known_first_party=["kedro_mlflow"]
+known_third_party=["anyconfig","black","click","cookiecutter","flake8","isort","jinja2","kedro","mlflow","pandas","pytest","pytest_lazyfixture","setuptools","sklearn","yaml"]
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 00000000..92dd5cbc
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,2 @@
+mlflow>=1.0.0, <2.0.0
+kedro>=0.16.0, <0.17.0
diff --git a/requirements/docs_requirements.txt b/requirements/docs_requirements.txt
deleted file mode 100644
index 71dd8b3f..00000000
--- a/requirements/docs_requirements.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-sphinx==3.1.2
-recommonmark==0.6.0
-sphinx_rtd_theme==0.5.0
-sphinx-markdown-tables==0.0.15
diff --git a/requirements/requirements.txt b/requirements/requirements.txt
deleted file mode 100644
index 16f98109..00000000
--- a/requirements/requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-mlflow>=1.0.0, <2.0.0
-kedro>=0.16.0, <=0.17.0
diff --git a/setup.cfg b/setup.cfg
index 1188a504..cfaee490 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,2 +1,28 @@
+[bumpversion]
+current_version = 0.5.0
+
 [tool:pytest]
 addopts = -s --cov=kedro_mlflow --cov-report=html tests/
+
+[flake8]
+ignore = E203, E266, E501, W503
+max-line-length = 88
+max-complexity = 18
+select = B,C,E,F,W,T4,B9
+per-file-ignores = **/__init__.py:F401
+
+[bumpversion:file:setup.py]
+
+[bumpversion:file:kedro_mlflow/__init__.py]
+
+[bumpversion:file:README.md]
+
+[bumpversion:file:docs/source/01_introduction/02_motivation.md]
+
+[bumpversion:file:docs/source/02_installation/01_installation.md]
+
+[bumpversion:file:docs/source/03_getting_started/01_example_project.md]
+
+[bumpversion:file:docs/source/04_experimentation_tracking/02_version_parameters.md]
+
+[bumpversion:file:docs/source/04_experimentation_tracking/03_version_datasets.md]
diff --git a/setup.py b/setup.py
index 234b630d..236d1706 100644
--- a/setup.py
+++ b/setup.py
@@ -15,8 +15,7 @@ def _parse_requirements(path, encoding="utf-8"):
 
 
 # get the dependencies and installs
-base_requirements = _parse_requirements("requirements/requirements.txt")
-test_requirements = _parse_requirements("requirements/test_requirements.txt")
+base_requirements = _parse_requirements("requirements.txt")
 
 
 # Get the long description from the README file
@@ -26,7 +25,7 @@ def _parse_requirements(path, encoding="utf-8"):
 
 setup(
     name=NAME,
-    version="0.4.1",  # this will be bumped automatically by bump2version
+    version="0.5.0",  # this will be bumped automatically by bump2version
     description="A kedro-plugin to use mlflow in your kedro projects",
     license="Apache Software License (Apache 2.0)",
     long_description=README,
@@ -36,8 +35,27 @@ def _parse_requirements(path, encoding="utf-8"):
     packages=find_packages(exclude=["docs*", "tests*"]),
     setup_requires=["setuptools_scm"],
     include_package_data=True,
-    tests_require=test_requirements,
     install_requires=base_requirements,
+    extras_require={
+        "docs": [
+            "sphinx==3.4.3",
+            "recommonmark==0.7.1",
+            "sphinx_rtd_theme==0.5.1",
+            "sphinx-markdown-tables==0.0.15",
+            "pandas>=1.0.0, <1.3.0",  # avoid to make readthedocs load rc version
+            "numpy>=1.0.0, <1.19.0",  # bug on windows for numpy 1.19.0->1.19.4
+        ],
+        "tests": [
+            "pytest>=5.4.0, <7.0.0",
+            "pytest-cov>=2.8.0, <3.0.0",
+            "pytest-lazy-fixture>=0.6.0, <1.0.0",
+            "pytest-mock>=3.1.0, <4.0.0",
+            "scikit-learn>=0.23.0, <0.25.0",
+            "flake8==3.8.4",  # ensure consistency with pre-commit
+            "black==20.8b1",  # pin black version because it is not compatible with a pip range (because of non semver version number)
+            "isort==5.7.0",  # ensure consistency with pre-commit
+        ],
+    },
     author="Galileo-Galilei",
     entry_points={
         "kedro.project_commands": [


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index d0cf03b5..304674aa 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -5,12 +5,12 @@ name: test
 
 on:
   push:
-    branches: [ develop, master ]
+    branches: [develop, master]
   pull_request:
-    branches: [ develop, master ]
+    branches: [develop, master]
 
 jobs:
-  build:
+  lint_and_test:
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
@@ -28,8 +28,7 @@ jobs:
     - name: Install dependencies
       run: |
         python -m pip install --upgrade pip
-        pip install -r requirements/requirements.txt
-        pip install -r requirements/test_requirements.txt
+        pip install -e .[tests]
     - name: Lint with flake8
       run: |
         # stop the build if there are Python syntax errors or undefined names
@@ -38,11 +37,18 @@ jobs:
         flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --exclude kedro_mlflow/template/project/run.py
     - name: Test with pytest and generate coverage report
       run: |
-        pip install -e .  # required for some tests which launches commands via subprocess
         pytest --cov=./ --cov-report=xml
     - name: Upload coverage report to Codecov
       uses: codecov/codecov-action@v1
       with:
+        token: ${{ secrets.CODECOV_TOKEN }}  # token is not mandatory but make access more stable
         file: ./coverage.xml
         env_vars: OS,PYTHON
         fail_ci_if_error: true
+  markdown-link-check:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+    - uses: gaurav-nelson/github-action-markdown-link-check@v1
+      with:
+        config-file: 'mlc_config.json'
diff --git a/requirements/test_requirements.txt b/requirements/test_requirements.txt
deleted file mode 100644
index 5c6ae5a0..00000000
--- a/requirements/test_requirements.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-pytest>=5.4.0, <6.0.0
-pytest-cov>=2.8.0, <3.0.0
-pytest-lazy-fixture>=0.6.0, <1.0.0
-pytest-mock>=3.1.0, <4.0.0
-scikit-learn>=0.23.0, <0.24.0
-flake8==3.8.4
-black==20.8b1  # pin black version because it is not compatible with a pip range (because of non semver version number)
-isort==5.6.4
diff --git a/tests/framework/cli/test_cli.py b/tests/framework/cli/test_cli.py
index 4080c03d..8da4c281 100644
--- a/tests/framework/cli/test_cli.py
+++ b/tests/framework/cli/test_cli.py
@@ -1,15 +1,18 @@
 import re
-import subprocess
+import subprocess  # noqa: F401
 
 import pytest
+import yaml
 from click.testing import CliRunner
 from cookiecutter.main import cookiecutter
 from kedro import __version__ as kedro_version
 from kedro.framework.cli.cli import TEMPLATE_PATH, info
+from kedro.framework.context import load_context
 
 from kedro_mlflow.framework.cli.cli import init as cli_init
 from kedro_mlflow.framework.cli.cli import mlflow_commands as cli_mlflow
 from kedro_mlflow.framework.cli.cli import ui as cli_ui
+from kedro_mlflow.framework.context import get_mlflow_config
 
 
 @pytest.fixture
@@ -63,22 +66,13 @@ def test_mlflow_commands_outside_kedro_project(monkeypatch, tmp_path):
     assert {"new"} == set(extract_cmd_from_help(result.output))
 
 
-def test_mlflow_commands_inside_non_initialized_kedro_project(
-    mocker, monkeypatch, tmp_path, kedro_project
-):
-    monkeypatch.chdir(tmp_path / "fake-project")
-    cli_runner = CliRunner()
-    result = cli_runner.invoke(cli_mlflow)
-    assert {"init"} == set(extract_cmd_from_help(result.output))
-    assert "You have not updated your template yet" in result.output
-
-
-def test_mlflow_commands_inside_initialized_kedro_project(
-    mocker, monkeypatch, tmp_path, kedro_project
+def test_mlflow_commands_inside_kedro_project(
+    monkeypatch,
+    tmp_path,
+    kedro_project,
 ):
     monkeypatch.chdir(tmp_path / "fake-project")
     # launch the command to initialize the project
-    subprocess.call(["kedro", "mlflow", "init"])
     cli_runner = CliRunner()
     result = cli_runner.invoke(cli_mlflow)
     assert {"init", "ui"} == set(extract_cmd_from_help(result.output))
@@ -97,8 +91,83 @@ def test_cli_init(monkeypatch, tmp_path, kedro_project):
     assert result.exit_code == 0
 
     # check mlflow.yml file
-    assert "'conf/base/mlflow.yml' successfully updated." in result.output
-    assert (project_path / "conf" / "base" / "mlflow.yml").is_file()
+    assert "'conf/local/mlflow.yml' successfully updated." in result.output
+    assert (project_path / "conf/local/mlflow.yml").is_file()
+
+
+def test_cli_init_existing_config(monkeypatch, tmp_path, kedro_project):
+    # "kedro_project" is a pytest.fixture declared in conftest
+    project_path = tmp_path / "fake-project"
+    monkeypatch.chdir(project_path)
+    cli_runner = CliRunner()
+
+    project_context = load_context(project_path.as_posix())
+    # emulate first call by writing a mlflow.yml file
+    yaml_str = yaml.dump(dict(mlflow_tracking_uri="toto"))
+    (project_path / project_context.CONF_ROOT / "local/mlflow.yml").write_text(yaml_str)
+
+    result = cli_runner.invoke(cli_init)
+
+    # check an error message is raised
+    assert "A 'mlflow.yml' already exists" in result.output
+
+    # check the file remains unmodified
+    assert get_mlflow_config(project_context).mlflow_tracking_uri.endswith("toto")
+
+
+def test_cli_init_existing_config_force_option(monkeypatch, tmp_path, kedro_project):
+    # "kedro_project" is a pytest.fixture declared in conftest
+    project_path = tmp_path / "fake-project"
+    monkeypatch.chdir(project_path)
+    cli_runner = CliRunner()
+
+    project_context = load_context(project_path.as_posix())
+    # emulate first call by writing a mlflow.yml file
+    yaml_str = yaml.dump(dict(mlflow_tracking_uri="toto"))
+    (project_path / project_context.CONF_ROOT / "local/mlflow.yml").write_text(yaml_str)
+
+    result = cli_runner.invoke(cli_init, args="--force")
+
+    # check an error message is raised
+    assert "successfully updated" in result.output
+
+    # check the file remains unmodified
+    assert get_mlflow_config(project_context).mlflow_tracking_uri.endswith("mlruns")
+
+
+@pytest.mark.parametrize(
+    "env",
+    ["base", "local"],
+)
+def test_cli_init_with_env(monkeypatch, tmp_path, kedro_project, env):
+    # "kedro_project" is a pytest.fixture declared in conftest
+    project_path = tmp_path / "fake-project"
+    monkeypatch.chdir(project_path)
+    cli_runner = CliRunner()
+    result = cli_runner.invoke(cli_init, f"--env {env}")
+
+    # FIRST TEST:
+    # the command should have executed propery
+    assert result.exit_code == 0
+
+    # check mlflow.yml file
+    assert f"'conf/{env}/mlflow.yml' successfully updated." in result.output
+    assert (project_path / "conf" / env / "mlflow.yml").is_file()
+
+
+@pytest.mark.parametrize(
+    "env",
+    ["debug"],
+)
+def test_cli_init_with_wrong_env(monkeypatch, tmp_path, kedro_project, env):
+    # "kedro_project" is a pytest.fixture declared in conftest
+    project_path = tmp_path / "fake-project"
+    monkeypatch.chdir(project_path)
+    cli_runner = CliRunner()
+    result = cli_runner.invoke(cli_init, f"--env {env}")
+
+    # A warning message should appear
+    assert f"No env '{env}' found" in result.output
 
 
 # TODO : This is a fake test. add a test to see if ui is properly up
diff --git a/tests/framework/context/test_config.py b/tests/framework/context/test_config.py
index b67baa30..10a0e9cd 100644
--- a/tests/framework/context/test_config.py
+++ b/tests/framework/context/test_config.py
@@ -64,6 +64,18 @@ def test_kedro_mlflow_config_init(tmp_path):
     )
 
 
+def test_kedro_mlflow_config_bad_long_parameters_strategy(tmp_path):
+    # create a ".kedro.yml" file to identify "tmp_path" as the root of a kedro project
+    (tmp_path / ".kedro.yml").write_text(yaml.dump(dict(context_path="fake/path")))
+    with pytest.raises(
+        KedroMlflowConfigError, match="'long_parameters_strategy' must be one of "
+    ):
+        KedroMlflowConfig(
+            project_path=tmp_path,
+            node_hook_opts=dict(long_parameters_strategy="does_not_exist"),
+        )
+
+
 def test_kedro_mlflow_config_new_experiment_does_not_exists(
     mocker, tmp_path, config_dir
 ):
diff --git a/tests/framework/context/test_mlflow_context.py b/tests/framework/context/test_mlflow_context.py
index cb06b426..26606fee 100644
--- a/tests/framework/context/test_mlflow_context.py
+++ b/tests/framework/context/test_mlflow_context.py
@@ -1,11 +1,9 @@
+import pytest
 import yaml
 from kedro.framework.context import load_context
 
 from kedro_mlflow.framework.context import get_mlflow_config
-
-# def test_get_mlflow_config_outside_kedro_project(tmp_path, config_with_base_mlflow_conf):
-#     with pytest.raises(KedroMlflowConfigError, match="not a valid path to a kedro project"):
-#         get_mlflow_config(project_path=tmp_path,env="local")
+from kedro_mlflow.framework.context.config import KedroMlflowConfigError
 
 
 def _write_yaml(filepath, config):
@@ -27,7 +25,14 @@ def test_get_mlflow_config(mocker, tmp_path, config_dir):
             experiment=dict(name="fake_package", create=True),
             run=dict(id="123456789", name="my_run", nested=True),
             ui=dict(port="5151", host="localhost"),
-            hooks=dict(node=dict(flatten_dict_params=True, recursive=False, sep="-")),
+            hooks=dict(
+                node=dict(
+                    flatten_dict_params=True,
+                    recursive=False,
+                    sep="-",
+                    long_parameters_strategy="truncate",
+                )
+            ),
         ),
     )
     expected = {
@@ -37,13 +42,30 @@ def test_get_mlflow_config(mocker, tmp_path, config_dir):
         "run": {"id": "123456789", "name": "my_run", "nested": True},
         "ui": {"port": "5151", "host": "localhost"},
         "hooks": {
-            "node": {"flatten_dict_params": True, "recursive": False, "sep": "-"}
+            "node": {
+                "flatten_dict_params": True,
+                "recursive": False,
+                "sep": "-",
+                "long_parameters_strategy": "truncate",
+            }
         },
     }
     context = load_context(tmp_path)
     assert get_mlflow_config(context).to_dict() == expected
 
 
+def test_get_mlflow_config_in_uninitialized_project(mocker, tmp_path, config_dir):
+    # config_with_base_mlflow_conf is a pytest.fixture in conftest
+    mocker.patch("logging.config.dictConfig")
+    mocker.patch("kedro_mlflow.utils._is_kedro_project", return_value=True)
+
+    context = load_context(tmp_path)
+    with pytest.raises(
+        KedroMlflowConfigError, match="No 'mlflow.yml' config file found in environment"
+    ):
+        get_mlflow_config(context)
+
+
 def test_mlflow_config_with_templated_config(mocker, tmp_path, config_dir):
 
     _write_yaml(
@@ -54,7 +76,14 @@ def test_mlflow_config_with_templated_config(mocker, tmp_path, config_dir):
             experiment=dict(name="fake_package", create=True),
             run=dict(id="123456789", name="my_run", nested=True),
             ui=dict(port="5151", host="localhost"),
-            hooks=dict(node=dict(flatten_dict_params=True, recursive=False, sep="-")),
+            hooks=dict(
+                node=dict(
+                    flatten_dict_params=True,
+                    recursive=False,
+                    sep="-",
+                    long_parameters_strategy="truncate",
+                )
+            ),
         ),
     )
 
@@ -70,7 +99,12 @@ def test_mlflow_config_with_templated_config(mocker, tmp_path, config_dir):
         "run": {"id": "123456789", "name": "my_run", "nested": True},
         "ui": {"port": "5151", "host": "localhost"},
         "hooks": {
-            "node": {"flatten_dict_params": True, "recursive": False, "sep": "-"}
+            "node": {
+                "flatten_dict_params": True,
+                "recursive": False,
+                "sep": "-",
+                "long_parameters_strategy": "truncate",
+            }
         },
     }
 
diff --git a/tests/framework/hooks/test_node_hook.py b/tests/framework/hooks/test_node_hook.py
index 6997b18d..0d3d6615 100644
--- a/tests/framework/hooks/test_node_hook.py
+++ b/tests/framework/hooks/test_node_hook.py
@@ -7,6 +7,7 @@
 from kedro.io import DataCatalog, MemoryDataSet
 from kedro.pipeline import Pipeline, node
 from mlflow.tracking import MlflowClient
+from mlflow.utils.validation import MAX_PARAM_VAL_LENGTH
 
 from kedro_mlflow.framework.hooks import MlflowNodeHook
 from kedro_mlflow.framework.hooks.node_hook import flatten_dict
@@ -196,3 +197,192 @@ def test_node_hook_logging(
     mlflow_client = MlflowClient(mlflow_tracking_uri)
     current_run = mlflow_client.get_run(run_id)
     assert current_run.data.params == expected
+
+
+@pytest.mark.parametrize(
+    "param_length", [MAX_PARAM_VAL_LENGTH - 10, MAX_PARAM_VAL_LENGTH]
+)
+@pytest.mark.parametrize("strategy", ["fail", "truncate", "tag"])
+def test_node_hook_logging_below_limit_all_strategy(
+    tmp_path, config_dir, dummy_run_params, dummy_node, param_length, strategy
+):
+
+    # mocker.patch("kedro_mlflow.utils._is_kedro_project", return_value=True)
+
+    _write_yaml(
+        tmp_path / "conf" / "base" / "mlflow.yml",
+        dict(
+            hooks=dict(node=dict(long_parameters_strategy=strategy)),
+        ),
+    )
+
+    mlflow_tracking_uri = (tmp_path / "mlruns").as_uri()
+    mlflow.set_tracking_uri(mlflow_tracking_uri)
+
+    mlflow_node_hook = MlflowNodeHook()
+
+    param_value = param_length * "a"
+    node_inputs = {"params:my_param": param_value}
+
+    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(),  # can be empty
+            inputs=node_inputs,
+            is_async=False,
+            run_id="132",
+        )
+        run_id = mlflow.active_run().info.run_id
+
+    mlflow_client = MlflowClient(mlflow_tracking_uri)
+    current_run = mlflow_client.get_run(run_id)
+    assert current_run.data.params == {"my_param": param_value}
+
+
+@pytest.mark.parametrize(
+    "param_length",
+    [MAX_PARAM_VAL_LENGTH + 20],
+)
+def test_node_hook_logging_above_limit_truncate_strategy(
+    tmp_path, config_dir, dummy_run_params, dummy_node, param_length
+):
+
+    # mocker.patch("kedro_mlflow.utils._is_kedro_project", return_value=True)
+
+    _write_yaml(
+        tmp_path / "conf" / "base" / "mlflow.yml",
+        dict(
+            hooks=dict(node=dict(long_parameters_strategy="truncate")),
+        ),
+    )
+
+    mlflow_tracking_uri = (tmp_path / "mlruns").as_uri()
+    mlflow.set_tracking_uri(mlflow_tracking_uri)
+
+    mlflow_node_hook = MlflowNodeHook()
+
+    param_value = param_length * "a"
+    node_inputs = {"params:my_param": param_value}
+
+    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(),  # can be empty
+            inputs=node_inputs,
+            is_async=False,
+            run_id="132",
+        )
+        run_id = mlflow.active_run().info.run_id
+
+    mlflow_client = MlflowClient(mlflow_tracking_uri)
+    current_run = mlflow_client.get_run(run_id)
+    assert current_run.data.params == {"my_param": param_value[0:MAX_PARAM_VAL_LENGTH]}
+
+
+@pytest.mark.parametrize(
+    "param_length",
+    [MAX_PARAM_VAL_LENGTH + 20],
+)
+def test_node_hook_logging_above_limit_fail_strategy(
+    tmp_path, config_dir, dummy_run_params, dummy_node, param_length
+):
+
+    # mocker.patch("kedro_mlflow.utils._is_kedro_project", return_value=True)
+
+    _write_yaml(
+        tmp_path / "conf" / "base" / "mlflow.yml",
+        dict(
+            hooks=dict(node=dict(long_parameters_strategy="fail")),
+        ),
+    )
+
+    mlflow_tracking_uri = (tmp_path / "mlruns").as_uri()
+    mlflow.set_tracking_uri(mlflow_tracking_uri)
+
+    mlflow_node_hook = MlflowNodeHook()
+
+    param_value = param_length * "a"
+    node_inputs = {"params:my_param": param_value}
+
+    with mlflow.start_run():
+        mlflow_node_hook.before_pipeline_run(
+            run_params=dummy_run_params, pipeline=Pipeline([]), catalog=DataCatalog()
+        )
+
+        # IMPORTANT: Overpassing the parameters limit
+        # should raise an error for all mlflow backend
+        # but it does not on FileStore backend :
+        # https://github.com/mlflow/mlflow/issues/2814#issuecomment-628284425
+        # Since we use FileStore system for simplicty for tests logging works
+        # But we have enforced failure (which is slightly different from mlflow
+        # behaviour)
+        with pytest.raises(
+            ValueError, match=f"Parameter 'my_param' length is {param_length}"
+        ):
+            mlflow_node_hook.before_node_run(
+                node=node(func=lambda x: x, inputs=dict(x="a"), outputs=None),
+                catalog=DataCatalog(),  # can be empty
+                inputs=node_inputs,
+                is_async=False,
+                run_id="132",
+            )
+
+
+@pytest.mark.parametrize(
+    "param_length",
+    [MAX_PARAM_VAL_LENGTH + 20],
+)
+def test_node_hook_logging_above_limit_tag_strategy(
+    tmp_path, config_dir, dummy_run_params, dummy_node, param_length
+):
+
+    # mocker.patch("kedro_mlflow.utils._is_kedro_project", return_value=True)
+
+    _write_yaml(
+        tmp_path / "conf" / "base" / "mlflow.yml",
+        dict(
+            hooks=dict(node=dict(long_parameters_strategy="tag")),
+        ),
+    )
+
+    mlflow_tracking_uri = (tmp_path / "mlruns").as_uri()
+    mlflow.set_tracking_uri(mlflow_tracking_uri)
+
+    mlflow_node_hook = MlflowNodeHook()
+
+    param_value = param_length * "a"
+    node_inputs = {"params:my_param": param_value}
+
+    with mlflow.start_run():
+        mlflow_node_hook.before_pipeline_run(
+            run_params=dummy_run_params, pipeline=Pipeline([]), catalog=DataCatalog()
+        )
+
+        # IMPORTANT: Overpassing the parameters limit
+        # should raise an error for all mlflow backend
+        # but it does not on FileStore backend :
+        # https://github.com/mlflow/mlflow/issues/2814#issuecomment-628284425
+        # Since we use FileStore system for simplicty for tests logging works
+        # But we have enforced failure (which is slightly different from mlflow
+        # behaviour)
+        mlflow_node_hook.before_node_run(
+            node=node(func=lambda x: x, inputs=dict(x="a"), outputs=None),
+            catalog=DataCatalog(),  # can be empty
+            inputs=node_inputs,
+            is_async=False,
+            run_id="132",
+        )
+        run_id = mlflow.active_run().info.run_id
+
+    mlflow_client = MlflowClient(mlflow_tracking_uri)
+    current_run = mlflow_client.get_run(run_id)
+    assert current_run.data.params == {}
+    assert {
+        k: v for k, v in current_run.data.tags.items() if not k.startswith("mlflow")
+    } == {"my_param": param_value}
diff --git a/tests/framework/hooks/test_pipeline_hook.py b/tests/framework/hooks/test_pipeline_hook.py
index ed6a47cd..652665c4 100644
--- a/tests/framework/hooks/test_pipeline_hook.py
+++ b/tests/framework/hooks/test_pipeline_hook.py
@@ -199,6 +199,60 @@ def dummy_catalog(tmp_path):
     return dummy_catalog
 
 
+@pytest.fixture
+def pipeline_ml_with_parameters():
+    def remove_stopwords(data, stopwords):
+        return data
+
+    def train_fun_hyperparam(data, hyperparam):
+        return 2
+
+    def predict_fun(model, data):
+        return data * model
+
+    def convert_probs_to_pred(data, threshold):
+        return (data > threshold) * 1
+
+    full_pipeline = Pipeline(
+        [
+            # almost the same that previsously but stopwords are parameters
+            # this is a shared parameter between inference and training22
+            node(
+                func=remove_stopwords,
+                inputs=dict(data="data", stopwords="params:stopwords"),
+                outputs="cleaned_data",
+                tags=["training", "inference"],
+            ),
+            # parameters in training pipeline, should not be persisted
+            node(
+                func=train_fun_hyperparam,
+                inputs=["cleaned_data", "params:penalty"],
+                outputs="model",
+                tags=["training"],
+            ),
+            node(
+                func=predict_fun,
+                inputs=["model", "cleaned_data"],
+                outputs="predicted_probs",
+                tags=["inference"],
+            ),
+            # this time, there is a parameter only for the inference pipeline
+            node(
+                func=convert_probs_to_pred,
+                inputs=["predicted_probs", "params:threshold"],
+                outputs="predictions",
+                tags=["inference"],
+            ),
+        ]
+    )
+    pipeline_ml_with_parameters = pipeline_ml_factory(
+        training=full_pipeline.only_nodes_with_tags("training"),
+        inference=full_pipeline.only_nodes_with_tags("inference"),
+        input_name="data",
+    )
+    return pipeline_ml_with_parameters
+
+
 @pytest.fixture
 def dummy_signature(dummy_catalog, dummy_pipeline_ml):
     input_data = dummy_catalog.load(dummy_pipeline_ml.input_name)
@@ -317,12 +371,80 @@ def test_mlflow_pipeline_hook_with_different_pipeline_types(
         }
 
 
+@pytest.mark.parametrize(
+    "copy_mode,expected",
+    [
+        (None, {"raw_data": None, "data": None, "model": None}),
+        ("assign", {"raw_data": "assign", "data": "assign", "model": "assign"}),
+        ("deepcopy", {"raw_data": "deepcopy", "data": "deepcopy", "model": "deepcopy"}),
+        ({"model": "assign"}, {"raw_data": None, "data": None, "model": "assign"}),
+    ],
+)
+def test_mlflow_pipeline_hook_with_copy_mode(
+    mocker,
+    monkeypatch,
+    tmp_path,
+    config_dir,
+    dummy_pipeline_ml,
+    dummy_catalog,
+    dummy_run_params,
+    dummy_mlflow_conf,
+    copy_mode,
+    expected,
+):
+    # config_with_base_mlflow_conf is a conftest fixture
+    mocker.patch("kedro_mlflow.utils._is_kedro_project", return_value=True)
+    monkeypatch.chdir(tmp_path)
+    pipeline_hook = MlflowPipelineHook()
+    runner = SequentialRunner()
+
+    pipeline_hook.after_catalog_created(
+        catalog=dummy_catalog,
+        # `after_catalog_created` is not using any of arguments bellow,
+        # so we are setting them to empty values.
+        conf_catalog={},
+        conf_creds={},
+        feed_dict={},
+        save_version="",
+        load_versions="",
+        run_id=dummy_run_params["run_id"],
+    )
+
+    pipeline_to_run = pipeline_ml_factory(
+        training=dummy_pipeline_ml.training,
+        inference=dummy_pipeline_ml.inference,
+        input_name=dummy_pipeline_ml.input_name,
+        conda_env={},
+        model_name=dummy_pipeline_ml.model_name,
+        copy_mode=copy_mode,
+    )
+    pipeline_hook.before_pipeline_run(
+        run_params=dummy_run_params, pipeline=pipeline_to_run, catalog=dummy_catalog
+    )
+    runner.run(pipeline_to_run, dummy_catalog)
+    run_id = mlflow.active_run().info.run_id
+    pipeline_hook.after_pipeline_run(
+        run_params=dummy_run_params, pipeline=pipeline_to_run, catalog=dummy_catalog
+    )
+
+    mlflow_tracking_uri = (tmp_path / "mlruns").as_uri()
+    mlflow.set_tracking_uri(mlflow_tracking_uri)
+
+    loaded_model = mlflow.pyfunc.load_model(model_uri=f"runs:/{run_id}/model")
+
+    actual_copy_mode = {
+        name: ds._copy_mode
+        for name, ds in loaded_model._model_impl.python_model.loaded_catalog._data_sets.items()
+    }
+
+    assert actual_copy_mode == expected
+
+
 def test_mlflow_pipeline_hook_metrics_with_run_id(
     mocker,
     monkeypatch,
     tmp_path,
     config_dir,
-    env_from_dict,
     dummy_pipeline_ml,
     dummy_run_params,
     dummy_mlflow_conf,
@@ -395,6 +517,74 @@ def test_mlflow_pipeline_hook_metrics_with_run_id(
     assert run_data.metrics["foo.metric_key"] == 1.1
 
 
+def test_mlflow_pipeline_hook_save_pipeline_ml_with_parameters(
+    mocker,
+    monkeypatch,
+    config_dir,  # a fixture to be in a kedro project
+    dummy_mlflow_conf,  # a fixture to setup mlflow configuration
+    tmp_path,
+    pipeline_ml_with_parameters,
+    dummy_run_params,
+):
+    # config_with_base_mlflow_conf is a conftest fixture
+    monkeypatch.chdir(tmp_path)
+
+    context = load_context(tmp_path)
+    mlflow_conf = get_mlflow_config(context)
+    mlflow.set_tracking_uri(mlflow_conf.mlflow_tracking_uri)
+
+    catalog_with_parameters = DataCatalog(
+        {
+            "data": MemoryDataSet(pd.DataFrame(data=[1], columns=["a"])),
+            "cleaned_data": MemoryDataSet(),
+            "params:stopwords": MemoryDataSet(["Hello", "Hi"]),
+            "params:penalty": MemoryDataSet(0.1),
+            "model": PickleDataSet((tmp_path / "model.csv").as_posix()),
+            "params:threshold": MemoryDataSet(0.5),
+        }
+    )
+
+    pipeline_hook = MlflowPipelineHook()
+
+    runner = SequentialRunner()
+    pipeline_hook.after_catalog_created(
+        catalog=catalog_with_parameters,
+        # `after_catalog_created` is not using any of arguments bellow,
+        # so we are setting them to empty values.
+        conf_catalog={},
+        conf_creds={},
+        feed_dict={},
+        save_version="",
+        load_versions="",
+        run_id=dummy_run_params["run_id"],
+    )
+    pipeline_hook.before_pipeline_run(
+        run_params=dummy_run_params,
+        pipeline=pipeline_ml_with_parameters,
+        catalog=catalog_with_parameters,
+    )
+    runner.run(pipeline_ml_with_parameters, catalog_with_parameters)
+
+    current_run_id = mlflow.active_run().info.run_id
+
+    # This is what we want to test: model must be saved and the parameters automatically persisted on disk
+    pipeline_hook.after_pipeline_run(
+        run_params=dummy_run_params,
+        pipeline=pipeline_ml_with_parameters,
+        catalog=catalog_with_parameters,
+    )
+
+    # the 2 parameters which are inputs of inference pipeline
+    # must have been persisted and logged inside the model's artifacts
+    model = mlflow.pyfunc.load_model(f"runs:/{current_run_id}/model")
+    assert set(
+        model.metadata.to_dict()["flavors"]["python_function"]["artifacts"].keys()
+    ) == {"model", "params:stopwords", "params:threshold"}
+
+    # the model should be loadable and predict() should work (this tests KedroPipelineModel)
+    assert model.predict(pd.DataFrame(data=[1], columns=["a"])).values[0][0] == 1
+
+
 @pytest.mark.parametrize(
     "model_signature,expected_signature",
     (
diff --git a/tests/io/models/test_mlflow_model_logger_dataset.py b/tests/io/models/test_mlflow_model_logger_dataset.py
index c462a3fd..ab87d9aa 100644
--- a/tests/io/models/test_mlflow_model_logger_dataset.py
+++ b/tests/io/models/test_mlflow_model_logger_dataset.py
@@ -1,3 +1,5 @@
+from tempfile import TemporaryDirectory
+
 import mlflow
 import pandas as pd
 import pytest
@@ -29,6 +31,12 @@ def tracking_uri(tmp_path):
     return tracking_uri
 
 
+@pytest.fixture
+def tmp_folder():
+    tmp_folder = TemporaryDirectory()
+    return tmp_folder
+
+
 @pytest.fixture
 def mlflow_client(tracking_uri):
     mlflow_client = MlflowClient(tracking_uri=tracking_uri)
@@ -275,10 +283,15 @@ def test_load_without_run_id_nor_active_run():
 
 
 def test_pyfunc_flavor_python_model_save_and_load(
-    tmp_path, tracking_uri, pipeline_ml_obj, dummy_catalog, kedro_pipeline_model
+    tmp_path,
+    tmp_folder,
+    tracking_uri,
+    pipeline_ml_obj,
+    dummy_catalog,
+    kedro_pipeline_model,
 ):
 
-    artifacts = pipeline_ml_obj.extract_pipeline_artifacts(dummy_catalog)
+    artifacts = pipeline_ml_obj.extract_pipeline_artifacts(dummy_catalog, tmp_folder)
 
     model_config = {
         "name": "kedro_pipeline_model",
diff --git a/tests/io/models/test_mlflow_model_saver_dataset.py b/tests/io/models/test_mlflow_model_saver_dataset.py
index c640cf7c..8fded59d 100644
--- a/tests/io/models/test_mlflow_model_saver_dataset.py
+++ b/tests/io/models/test_mlflow_model_saver_dataset.py
@@ -1,3 +1,5 @@
+from tempfile import TemporaryDirectory
+
 import mlflow
 import pandas as pd
 import pytest
@@ -17,6 +19,12 @@ def linreg_model():
     return linreg_model
 
 
+@pytest.fixture
+def tmp_folder():
+    tmp_folder = TemporaryDirectory()
+    return tmp_folder
+
+
 @pytest.fixture
 def linreg_path(tmp_path):
     linreg_path = tmp_path / "data" / "06_models" / "linreg"
@@ -134,10 +142,10 @@ def test_save_load_local(linreg_path, linreg_model, versioned):
 
 
 def test_pyfunc_flavor_python_model_save_and_load(
-    tmp_path, pipeline_ml_obj, dummy_catalog, kedro_pipeline_model
+    tmp_path, tmp_folder, pipeline_ml_obj, dummy_catalog, kedro_pipeline_model
 ):
 
-    artifacts = pipeline_ml_obj.extract_pipeline_artifacts(dummy_catalog)
+    artifacts = pipeline_ml_obj.extract_pipeline_artifacts(dummy_catalog, tmp_folder)
 
     model_config = {
         "name": "kedro_pipeline_model",
diff --git a/tests/mlflow/test_kedro_pipeline_model.py b/tests/mlflow/test_kedro_pipeline_model.py
index bbf0acc3..83a9105f 100644
--- a/tests/mlflow/test_kedro_pipeline_model.py
+++ b/tests/mlflow/test_kedro_pipeline_model.py
@@ -1,4 +1,5 @@
 from pathlib import Path
+from tempfile import TemporaryDirectory
 
 import mlflow
 import pandas as pd
@@ -13,6 +14,12 @@
 from kedro_mlflow.pipeline import pipeline_ml_factory
 
 
+@pytest.fixture
+def tmp_folder():
+    tmp_folder = TemporaryDirectory()
+    return tmp_folder
+
+
 @pytest.fixture
 def pipeline_ml_obj():
     def preprocess_fun(data):
@@ -51,9 +58,9 @@ def predict_fun(model, data):
     return pipeline_ml_obj
 
 
-def test_model_packaging(tmp_path, pipeline_ml_obj):
-
-    catalog = DataCatalog(
+@pytest.fixture
+def dummy_catalog(tmp_path):
+    dummy_catalog = DataCatalog(
         {
             "raw_data": MemoryDataSet(),
             "data": MemoryDataSet(),
@@ -62,12 +69,29 @@ def test_model_packaging(tmp_path, pipeline_ml_obj):
             ),
         }
     )
+    return dummy_catalog
 
-    catalog._data_sets["model"].save(2)  # emulate model fitting
 
-    artifacts = pipeline_ml_obj.extract_pipeline_artifacts(catalog)
+@pytest.mark.parametrize(
+    "copy_mode,expected",
+    [
+        (None, {"raw_data": None, "data": None, "model": None}),
+        ("assign", {"raw_data": "assign", "data": "assign", "model": "assign"}),
+        ("deepcopy", {"raw_data": "deepcopy", "data": "deepcopy", "model": "deepcopy"}),
+        ({"model": "assign"}, {"raw_data": None, "data": None, "model": "assign"}),
+    ],
+)
+def test_model_packaging_with_copy_mode(
+    tmp_path, tmp_folder, pipeline_ml_obj, dummy_catalog, copy_mode, expected
+):
 
-    kedro_model = KedroPipelineModel(pipeline_ml=pipeline_ml_obj, catalog=catalog)
+    dummy_catalog._data_sets["model"].save(2)  # emulate model fitting
+
+    artifacts = pipeline_ml_obj.extract_pipeline_artifacts(dummy_catalog, tmp_folder)
+
+    kedro_model = KedroPipelineModel(
+        pipeline_ml=pipeline_ml_obj, catalog=dummy_catalog, copy_mode=copy_mode
+    )
 
     mlflow_tracking_uri = (tmp_path / "mlruns").as_uri()
     mlflow.set_tracking_uri(mlflow_tracking_uri)
@@ -80,16 +104,31 @@ def test_model_packaging(tmp_path, pipeline_ml_obj):
         )
         run_id = mlflow.active_run().info.run_id
 
-    loaded_model = mlflow.pyfunc.load_model(
-        model_uri=(Path(r"runs:/") / run_id / "model").as_posix()
-    )
+    loaded_model = mlflow.pyfunc.load_model(model_uri=f"runs:/{run_id}/model")
+
+    # first assertion: prediction works
     assert loaded_model.predict(1) == 2
 
+    # second assertion: copy_mode works
+    actual_copy_mode = {
+        name: ds._copy_mode
+        for name, ds in loaded_model._model_impl.python_model.loaded_catalog._data_sets.items()
+    }
+
+    assert actual_copy_mode == expected
+
+
+def test_kedro_pipeline_ml_with_wrong_copy_mode_type(pipeline_ml_obj, dummy_catalog):
+    with pytest.raises(TypeError, match="'copy_mode' must be a 'str' or a 'dict'"):
+        KedroPipelineModel(
+            pipeline_ml=pipeline_ml_obj, catalog=dummy_catalog, copy_mode=1346
+        )
+
 
 # should very likely add tests to see what happens when the artifacts
 # are incorrect
 # incomplete
-# contains to input_name
+# contains no input_name
 # some memory datasets inside the catalog are persisted?
 
 
@@ -173,7 +212,7 @@ def test_model_packaging_missing_artifacts(tmp_path, pipeline_ml_obj):
         )
 
 
-def test_kedro_pipeline_ml_loading_deepcoiable_catalog(tmp_path):
+def test_kedro_pipeline_ml_loading_deepcoiable_catalog(tmp_path, tmp_folder):
 
     # create pipelien and catalog. The training will not be triggered
     def fit_fun(data):
@@ -217,7 +256,7 @@ def predict_fun(model, data):
     catalog = DataCatalog({"data": MemoryDataSet(), "model": model_dataset})
 
     kedro_model = KedroPipelineModel(pipeline_ml=ml_pipeline, catalog=catalog)
-    artifacts = ml_pipeline.extract_pipeline_artifacts(catalog)
+    artifacts = ml_pipeline.extract_pipeline_artifacts(catalog, tmp_folder)
 
     mlflow_tracking_uri = (tmp_path / "mlruns").as_uri()
     mlflow.set_tracking_uri(mlflow_tracking_uri)
diff --git a/tests/pipeline/test_pipeline_ml.py b/tests/pipeline/test_pipeline_ml.py
index d440a11f..d337d4e2 100644
--- a/tests/pipeline/test_pipeline_ml.py
+++ b/tests/pipeline/test_pipeline_ml.py
@@ -30,6 +30,10 @@ def train_fun(data):
     return 2
 
 
+def train_fun_hyperparam(data, hyperparam):
+    return 2
+
+
 def predict_fun(model, data):
     return data * model
 
@@ -46,6 +50,10 @@ def remove_stopwords(data, stopwords):
     return data
 
 
+def convert_probs_to_pred(data, threshold):
+    return data > threshold
+
+
 @pytest.fixture
 def pipeline_with_tag():
 
@@ -151,6 +159,48 @@ def pipeline_ml_with_inputs_artifacts():
     return pipeline_ml_with_inputs_artifacts
 
 
+@pytest.fixture
+def pipeline_ml_with_parameters():
+    full_pipeline = Pipeline(
+        [
+            # almost the same that previsously but stopwords are parameters
+            # this is a shared parameter between inference and training22
+            node(
+                func=remove_stopwords,
+                inputs=dict(data="data", stopwords="params:stopwords"),
+                outputs="cleaned_data",
+                tags=["training", "inference"],
+            ),
+            # parameters in training pipeline, should not be persisted
+            node(
+                func=train_fun_hyperparam,
+                inputs=["cleaned_data", "params:penalty"],
+                outputs="model",
+                tags=["training"],
+            ),
+            node(
+                func=predict_fun,
+                inputs=["model", "cleaned_data"],
+                outputs="predicted_probs",
+                tags=["inference"],
+            ),
+            # this time, there is a parameter only for the inference pipeline
+            node(
+                func=convert_probs_to_pred,
+                inputs=["predicted_probs", "params:threshold"],
+                outputs="predictions",
+                tags=["inference"],
+            ),
+        ]
+    )
+    pipeline_ml_with_parameters = pipeline_ml_factory(
+        training=full_pipeline.only_nodes_with_tags("training"),
+        inference=full_pipeline.only_nodes_with_tags("inference"),
+        input_name="data",
+    )
+    return pipeline_ml_with_parameters
+
+
 @pytest.fixture
 def dummy_context(tmp_path, config_dir, mocker):
     class DummyContext(KedroContext):
@@ -206,6 +256,21 @@ def catalog_with_stopwords():
     return catalog_with_stopwords
 
 
+@pytest.fixture
+def catalog_with_parameters():
+    catalog_with_parameters = DataCatalog(
+        {
+            "data": MemoryDataSet(),
+            "cleaned_data": MemoryDataSet(),
+            "params:stopwords": MemoryDataSet(["Hello", "Hi"]),
+            "params:penalty": MemoryDataSet(0.1),
+            "model": CSVDataSet("fake/path/to/model.csv"),
+            "params:threshold": MemoryDataSet(0.5),
+        }
+    )
+    return catalog_with_parameters
+
+
 @pytest.mark.parametrize(
     "tags,from_nodes,to_nodes,node_names,from_inputs",
     [
@@ -333,6 +398,16 @@ def test_filtering_generate_invalid_pipeline_ml(
             pytest.lazy_fixture("catalog_with_stopwords"),
             {"model", "data", "stopwords_from_nltk"},
         ),
+        (
+            pytest.lazy_fixture("pipeline_ml_with_parameters"),
+            pytest.lazy_fixture("catalog_with_parameters"),
+            {
+                "model",
+                "data",
+                "params:stopwords",
+                "params:threshold",
+            },
+        ),
     ],
 )
 def test_catalog_extraction(pipeline_ml_obj, catalog, result):
diff --git a/tests/test_utils.py b/tests/test_utils.py
index abbbb351..f1b9b463 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,12 +1,9 @@
 from pathlib import Path
 
 import pytest
-import yaml
 
 from kedro_mlflow.utils import (
     KEDRO_VERSION,
-    KedroContextError,
-    _already_updated,
     _get_project_globals,
     _is_kedro_project,
     _parse_requirements,
@@ -41,35 +38,6 @@ def test_validate_project_path(project_path, project_path_expected):
     assert project_path_result == project_path_expected
 
 
-def test_already_updated(tmp_path):
-    def _write_yaml(filepath, config):
-        filepath.parent.mkdir(parents=True, exist_ok=True)
-        yaml_str = yaml.dump(config)
-        filepath.write_text(yaml_str)
-
-    _write_yaml(
-        tmp_path / "conf" / "base" / "mlflow.yml",
-        dict(
-            mlflow_tracking_uri=(tmp_path / "mlruns").as_posix(),
-            experiment=dict(name="Default", create=True),
-            run=dict(id=None, name=None, nested=True),
-            ui=dict(port=None, host=None),
-        ),
-    )
-    flag = _already_updated(tmp_path)
-    expected_flag = True
-
-    return flag == expected_flag
-
-
-def test_not_yet_updated(tmp_path):
-
-    flag = _already_updated(tmp_path)
-    expected_flag = False
-
-    return flag == expected_flag
-
-
 @pytest.mark.parametrize(
     "project,is_kedro_project",
     (