From a53500afe5cd9739362b066e24771cd26b0c63bb Mon Sep 17 00:00:00 2001 From: "L. R. Couto" <57910428+lrcouto@users.noreply.github.com> Date: Tue, 21 May 2024 14:49:08 -0300 Subject: [PATCH] Make it possible to use Kedro after uninstalling Rich. (#3838) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * attempt to configure fallback logger Signed-off-by: lrcouto * Set up fallback for logging on the logging function itself Signed-off-by: lrcouto * Lint Signed-off-by: lrcouto * Lint Signed-off-by: lrcouto * Update kedro/logging.py Co-authored-by: Sajid Alam <90610031+SajidAlamQB@users.noreply.github.com> Signed-off-by: L. R. Couto <57910428+lrcouto@users.noreply.github.com> * Changes to rich variable on logging Signed-off-by: lrcouto * Lint Signed-off-by: lrcouto * attempt to configure fallback logger Signed-off-by: lrcouto * Set up fallback for logging on the logging function itself Signed-off-by: lrcouto * Lint Signed-off-by: lrcouto * Lint Signed-off-by: lrcouto * Update kedro/logging.py Co-authored-by: Sajid Alam <90610031+SajidAlamQB@users.noreply.github.com> Signed-off-by: L. R. Couto <57910428+lrcouto@users.noreply.github.com> * Changes to rich variable on logging Signed-off-by: lrcouto * Lint Signed-off-by: lrcouto * adapt default logging Signed-off-by: lrcouto * Update kedro/logging.py Co-authored-by: Juan Luis Cano Rodríguez Signed-off-by: L. R. Couto <57910428+lrcouto@users.noreply.github.com> * Create separate logging config file for rich Signed-off-by: lrcouto * Hello linter my old friend Signed-off-by: lrcouto * Alternative for rich in kedro ipython Signed-off-by: lrcouto * Remove unnecessary try/except block Signed-off-by: lrcouto * Update pyproject.toml Co-authored-by: Juan Luis Cano Rodríguez Signed-off-by: L. R. Couto <57910428+lrcouto@users.noreply.github.com> * Resolve merge conflict Signed-off-by: lrcouto * Lint Signed-off-by: lrcouto * Fix config file paths Signed-off-by: lrcouto * Update kedro/ipython/__init__.py Co-authored-by: Deepyaman Datta Signed-off-by: L. R. Couto <57910428+lrcouto@users.noreply.github.com> * Update kedro/ipython/__init__.py Co-authored-by: Deepyaman Datta Signed-off-by: L. R. Couto <57910428+lrcouto@users.noreply.github.com> * Prevent kedro ipython from reimporting rich multiple times Signed-off-by: lrcouto * Logging config Signed-off-by: lrcouto * Change test config Signed-off-by: lrcouto * Remove ' yEs ' tests Signed-off-by: lrcouto * Tests to cover ipython changes Signed-off-by: lrcouto * Add test for import Signed-off-by: lrcouto * Ignore test coverage on import try/except Signed-off-by: lrcouto * Lint Signed-off-by: lrcouto * Unpin cookiecutter version Signed-off-by: lrcouto * Add changes to documentation Signed-off-by: lrcouto * Update RELEASE.md Co-authored-by: Merel Theisen <49397448+merelcht@users.noreply.github.com> Signed-off-by: L. R. Couto <57910428+lrcouto@users.noreply.github.com> * Change RICH variable name to RICH_INTALLED Signed-off-by: lrcouto * Add info about uninstalling rich on logging doc page Signed-off-by: lrcouto --------- Signed-off-by: lrcouto Signed-off-by: L. R. Couto <57910428+lrcouto@users.noreply.github.com> Co-authored-by: Sajid Alam <90610031+SajidAlamQB@users.noreply.github.com> Co-authored-by: Juan Luis Cano Rodríguez Co-authored-by: Deepyaman Datta Co-authored-by: Merel Theisen <49397448+merelcht@users.noreply.github.com> --- MANIFEST.in | 1 + RELEASE.md | 1 + docs/source/conf.py | 1 + .../configuration/configuration_basics.md | 18 +++++++++++++ docs/source/faq/faq.md | 1 + docs/source/logging/index.md | 18 +++++++++++++ kedro/framework/project/__init__.py | 9 ++++++- kedro/framework/project/default_logging.yml | 27 ++++++++++++++----- kedro/framework/project/rich_logging.yml | 18 +++++++++++++ kedro/ipython/__init__.py | 21 ++++++++++++--- kedro/logging.py | 4 --- tests/framework/cli/test_starters.py | 4 +-- tests/ipython/test_ipython.py | 15 +++++++++-- 13 files changed, 118 insertions(+), 20 deletions(-) create mode 100644 kedro/framework/project/rich_logging.yml diff --git a/MANIFEST.in b/MANIFEST.in index 3aaf04a960..86910941db 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ include README.md include LICENSE.md include kedro/framework/project/default_logging.yml +include kedro/framework/project/rich_logging.yml include kedro/ipython/*.png include kedro/ipython/*.svg recursive-include kedro/templates * diff --git a/RELEASE.md b/RELEASE.md index 8f36fd345d..fabff95266 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,6 +1,7 @@ # Upcoming Release 0.19.6 ## Major features and improvements +* It is now possible to use Kedro without having `rich` installed. ## Bug fixes and other changes * User defined catch-all dataset factory patterns now override the default pattern provided by the runner. diff --git a/docs/source/conf.py b/docs/source/conf.py index 21b9d6c4b4..49c0f119a7 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -234,6 +234,7 @@ # "anchor not found" but it's a valid selector for code examples "https://docs.delta.io/latest/delta-update.html#language-python", "https://github.com/kedro-org/kedro/blob/main/kedro/framework/project/default_logging.yml", + "https://github.com/kedro-org/kedro/blob/main/kedro/framework/project/rich_logging.yml", "https://github.com/kedro-org/kedro/blob/main/README.md#the-humans-behind-kedro", # "anchor not found" but is valid "https://opensource.org/license/apache2-0-php/", "https://docs.github.com/en/rest/overview/other-authentication-methods#via-username-and-password", diff --git a/docs/source/configuration/configuration_basics.md b/docs/source/configuration/configuration_basics.md index 7a4bebadee..ef69d149de 100644 --- a/docs/source/configuration/configuration_basics.md +++ b/docs/source/configuration/configuration_basics.md @@ -241,3 +241,21 @@ Customise the configuration loader arguments in `settings.py` as follows if your ```python CONFIG_LOADER_ARGS = {"default_run_env": "base"} ``` + +### How to use Kedro without the rich library + +If you prefer not to have the `rich` library in your Kedro project, you have the option to uninstall it. However, it's important to note that versions of the `cookiecutter` library above 2.3 have a dependency on rich. You will need to downgrade `cookiecutter` to a version below 2.3 to have Kedro work without `rich`. + +To uninstall the rich library, run: + +```bash +pip uninstall rich +``` + +To downgrade cookiecutter to a version that does not require rich, you can specify a version below 2.3. For example: + +```bash +pip install cookiecutter==2.2.0 +``` + +These changes will affect the visual appearance and formatting of Kedro's logging, prompts, and the output of the `kedro ipython` command. While using a version of `cookiecutter` below 2.3, the appearance of the prompts will be plain even with `rich` installed. diff --git a/docs/source/faq/faq.md b/docs/source/faq/faq.md index 6193d96740..fdb9502bff 100644 --- a/docs/source/faq/faq.md +++ b/docs/source/faq/faq.md @@ -35,6 +35,7 @@ This is a growing set of technical FAQs. The [product FAQs on the Kedro website] * [How do I specify additional configuration environments](../configuration/configuration_basics.md#how-to-specify-additional-configuration-environments)? * [How do I change the default overriding configuration environment](../configuration/configuration_basics.md#how-to-change-the-default-overriding-environment)? * [How do I use only one configuration environment](../configuration/configuration_basics.md#how-to-use-only-one-configuration-environment)? +* [How do I use Kedro without the rich library](../configuration/configuration_basics.md#how-to-use-kedro-without-the-rich-library)? ### Advanced topics diff --git a/docs/source/logging/index.md b/docs/source/logging/index.md index 7b0226e3bd..b38679a3c7 100644 --- a/docs/source/logging/index.md +++ b/docs/source/logging/index.md @@ -194,3 +194,21 @@ You must provide a value for both `COLUMNS` and `LINES` even if you only wish to ## How to enable rich logging in Jupyter Rich also formats the logs in JupyterLab and Jupyter Notebook. The size of the output console does not adapt to your window but can be controlled through the `JUPYTER_COLUMNS` and `JUPYTER_LINES` environment variables. The default values (115 and 100 respectively) should be suitable for most users, but if you require a different output console size then you should alter the values of `JUPYTER_COLUMNS` and `JUPYTER_LINES`. + +### How to use logging without the rich library + +If you prefer not to have the `rich` library in your Kedro project, you have the option to uninstall it. However, it's important to note that versions of the `cookiecutter` library above 2.3 have a dependency on rich. You will need to downgrade `cookiecutter` to a version below 2.3 to have Kedro work without `rich`. + +To uninstall the rich library, run: + +```bash +pip uninstall rich +``` + +To downgrade cookiecutter to a version that does not require rich, you can specify a version below 2.3. For example: + +```bash +pip install cookiecutter==2.2.0 +``` + +These changes will affect the visual appearance and formatting of Kedro's logging, prompts, and the output of the `kedro ipython` command. While using a version of `cookiecutter` below 2.3, the appearance of the prompts will be plain even with `rich` installed. diff --git a/kedro/framework/project/__init__.py b/kedro/framework/project/__init__.py index b996b08387..7cfd2a9957 100644 --- a/kedro/framework/project/__init__.py +++ b/kedro/framework/project/__init__.py @@ -224,7 +224,14 @@ def __init__(self) -> None: # Check if the default logging configuration exists default_logging_path = Path("conf/logging.yml") if not default_logging_path.exists(): - default_logging_path = Path(__file__).parent / "default_logging.yml" + default_logging_path = Path( + os.environ.get( + "KEDRO_LOGGING_CONFIG", + Path(__file__).parent / "rich_logging.yml" + if importlib.util.find_spec("rich") + else Path(__file__).parent / "default_logging.yml", + ) + ) # Use the user path if available, otherwise, use the default path if user_logging_path and Path(user_logging_path).exists(): diff --git a/kedro/framework/project/default_logging.yml b/kedro/framework/project/default_logging.yml index 87fae8a25c..27feb17542 100644 --- a/kedro/framework/project/default_logging.yml +++ b/kedro/framework/project/default_logging.yml @@ -2,17 +2,30 @@ version: 1 disable_existing_loggers: False +formatters: + simple: + format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + handlers: - rich: - class: kedro.logging.RichHandler - rich_tracebacks: True - # Advance options for customisation. - # See https://docs.kedro.org/en/stable/logging/logging.html#project-side-logging-configuration - # tracebacks_show_locals: False + console: + class: logging.StreamHandler + level: INFO + formatter: simple + stream: ext://sys.stdout + + info_file_handler: + class: logging.handlers.RotatingFileHandler + level: INFO + formatter: simple + filename: info.log + maxBytes: 10485760 # 10MB + backupCount: 20 + encoding: utf8 + delay: True loggers: kedro: level: INFO root: - handlers: [rich] + handlers: [console] diff --git a/kedro/framework/project/rich_logging.yml b/kedro/framework/project/rich_logging.yml new file mode 100644 index 0000000000..87fae8a25c --- /dev/null +++ b/kedro/framework/project/rich_logging.yml @@ -0,0 +1,18 @@ +version: 1 + +disable_existing_loggers: False + +handlers: + rich: + class: kedro.logging.RichHandler + rich_tracebacks: True + # Advance options for customisation. + # See https://docs.kedro.org/en/stable/logging/logging.html#project-side-logging-configuration + # tracebacks_show_locals: False + +loggers: + kedro: + level: INFO + +root: + handlers: [rich] diff --git a/kedro/ipython/__init__.py b/kedro/ipython/__init__.py index 182ff403f1..ee487582e8 100644 --- a/kedro/ipython/__init__.py +++ b/kedro/ipython/__init__.py @@ -5,6 +5,7 @@ from __future__ import annotations +import importlib import inspect import logging import os @@ -18,8 +19,12 @@ from IPython.core.getipython import get_ipython from IPython.core.magic import needs_local_scope, register_line_magic from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring -from rich.console import Console -from rich.syntax import Syntax + +try: + import rich.console as rich_console + import rich.syntax as rich_syntax +except ImportError: # pragma: no cover + pass from kedro.framework.cli import load_entry_points from kedro.framework.cli.project import CONF_SOURCE_HELP, PARAMS_ARG_HELP @@ -39,6 +44,8 @@ FunctionParameters = MappingProxyType +RICH_INSTALLED = True if importlib.util.find_spec("rich") is not None else False + def load_ipython_extension(ipython: Any) -> None: """ @@ -281,8 +288,14 @@ def _create_cell_with_text(text: str, is_jupyter: bool = True) -> None: def _print_cells(cells: list[str]) -> None: for cell in cells: - Console().print("") - Console().print(Syntax(cell, "python", theme="monokai", line_numbers=False)) + if RICH_INSTALLED is True: + rich_console.Console().print("") + rich_console.Console().print( + rich_syntax.Syntax(cell, "python", theme="monokai", line_numbers=False) + ) + else: + print("") # noqa: T201 + print(cell) # noqa: T201 def _load_node(node_name: str, pipelines: _ProjectPipelines) -> list[str]: diff --git a/kedro/logging.py b/kedro/logging.py index 72d85d41c5..83e8204fec 100644 --- a/kedro/logging.py +++ b/kedro/logging.py @@ -1,7 +1,3 @@ -""" -This module contains a logging handler class which produces coloured logs and tracebacks. -""" - import logging import sys from pathlib import Path diff --git a/tests/framework/cli/test_starters.py b/tests/framework/cli/test_starters.py index b4fb54d552..5d00fa5401 100644 --- a/tests/framework/cli/test_starters.py +++ b/tests/framework/cli/test_starters.py @@ -1119,7 +1119,7 @@ def test_invalid_tools_range(self, fake_kedro_cli, input): message = f"'{input}' is an invalid range for project tools.\nPlease ensure range values go from smaller to larger." assert message in result.output - @pytest.mark.parametrize("example_pipeline", ["y", "n", "N", "YEs", " yeS "]) + @pytest.mark.parametrize("example_pipeline", ["y", "n", "N", "YEs", " yEs "]) def test_valid_example(self, fake_kedro_cli, example_pipeline): result = CliRunner().invoke( fake_kedro_cli, @@ -1285,7 +1285,7 @@ def test_invalid_tools_flag_combination(self, fake_kedro_cli, input): in result.output ) - @pytest.mark.parametrize("example_pipeline", ["y", "n", "N", "YEs", " yeS "]) + @pytest.mark.parametrize("example_pipeline", ["y", "n", "N", "YEs", " yEs "]) def test_valid_example(self, fake_kedro_cli, example_pipeline): """Test project created from config.""" config = { diff --git a/tests/ipython/test_ipython.py b/tests/ipython/test_ipython.py index 333dc5e80e..1ab436e3a9 100644 --- a/tests/ipython/test_ipython.py +++ b/tests/ipython/test_ipython.py @@ -457,9 +457,20 @@ def test_load_node_with_ipython(self, mocker, ipython, run_env): ipython.magic("load_node dummy_node") spy.assert_called_once() - @pytest.mark.parametrize("run_env", ["databricks", "colab", "dummy"]) - def test_load_node_with_other(self, mocker, ipython, run_env): + @pytest.mark.parametrize( + "run_env, rich_installed", + [ + ("databricks", True), + ("databricks", False), + ("colab", True), + ("colab", False), + ("dummy", True), + ("dummy", False), + ], + ) + def test_load_node_with_other(self, mocker, ipython, run_env, rich_installed): mocker.patch("kedro.ipython._find_kedro_project") + mocker.patch("kedro.ipython.RICH_INSTALLED", rich_installed) mocker.patch("kedro.ipython._load_node", return_value=["cell1", "cell2"]) mocker.patch("kedro.ipython._guess_run_environment", return_value=run_env) spy = mocker.spy(kedro.ipython, "_print_cells")