# Making conda richer notes This project will require the following steps: 1. A refactor to replace all the usages of `print` and `sys.stdout.write` with a higher level abstraction that will be able to either route to the built in print function or to rich. 2. A settings mechanism that will allow us to turn rich `on` or `off` (this will default to `off`) 3. Implement our first rich enabled layout with the `conda info` command This refactor will give us a good opportunity to make sure that we are adhering to the principles laid out in this section: https://clig.dev/#output links: - https://github.com/ForgottenProgramme/rich-conda-mocks - https://github.com/conda/conda/issues/12766 - https://github.com/conda/conda/issues/13707 ## Refactoring - Finding all locations where stdout is printed (e.g. print statements, logging) - Planning how to refactor the UI elements such as `Spinner` and `ProgressBar` We also need to think about building an interface for plugins to use and endorse this as the official way to print things for users. ## Parts of the code to refactor ### Exception handler code: - https://github.com/conda/conda/blob/main/conda/exception_handler.py#L190 This should be a pretty easy part of the code to refactor. Plus, it will allow us to use rich's nice exception printing: - https://rich.readthedocs.io/en/stable/traceback.html ### Everything using `conda.cli.install` This is a view of everything that needs to be refactored that uses the `conda.cli.install` module. This include commands like, `install`, `create` and `update`. Components: - [Spinner](https://github.com/conda/conda/blob/main/conda/common/io.py#L377) - Used to display notices, repodata fetching and solving - [ProgressBar](https://github.com/conda/conda/blob/main/conda/common/io.py#L448) - [Update warning](https://github.com/conda/conda/blob/main/conda/core/solve.py#L1248-L1271) - Worth noting that conda-libmamba-solver has to be updated separately - [Package plan](https://github.com/conda/conda/blob/main/conda/core/link.py#L1242) - [Confirmation](https://github.com/conda/conda/blob/main/conda/cli/common.py#L57) - [Downloading and extracting packages](https://github.com/conda/conda/blob/main/conda/core/package_cache_data.py#L764) - [Environment activation](https://github.com/conda/conda/blob/main/conda/cli/install.py#L135) ### Other conda commands These commands should be considerably easier to refactor as there are not nearly as many moving parts as `install`, `create` and `update`. #### conda clean Components used: - Confirmation #### conda compare Just uses print statements #### conda config Just uses print statements. It should YAML which could be nicely formatted with `rich` #### conda info Uses print statements #### conda list Uses print statements #### conda notices Components: - Spinner #### conda package Listed as "experimental". Will not refactor. #### conda remove There's a couple print statements being used in this command (see `conda.cli.main_remove`) Components: - Spinner - For collection package metadata and transaction status - Package plan - Confirmation #### conda run This prints the output from the command that it runs. Does not need refactor #### conda search Customized print functions which is a lot like `conda info` Components: - Spinner - For collection of repodata.json ("Loading channels") ### Notes on components #### Progress Bar Updating this component to support either tqdm or rich will require a refactor of the `ProgressBar` class. I think the best way to do that will be to make a ProgressBarBase class which will serve as an abstract base class. The ReporterHandler will then have a method for retrieving this class based on the settings in `context.reporters`. ## Class design ### `ReporterHandler` The first abstract base class that will be written will be the `ReporterHandler` The abstract methods will correspond to display components in conda itself. As of now, they are the following: - `detail_view` (currently used in `conda info`) - `string_view` - `progress_bar` (used in `install`, `update`, `create`) - `spinner` (used in several commands) - `package_list` ```python from abc import ABC, abstractmethod class ReporterHandlerBase(ABC): """ Abstract Base Class for all backend/reporters. """ @abstractmethod def detail_view(): """method to display data in a tabular format""" ... @abstractmethod def string_view(): """method to display data like a regular string, e.g. `conda info --base` simply prints the root prefix string.""" ... class JsonHandler(ReporterHandlerBase): """class handling json output""" def detail_view(): ... def string_view(): ... class StdHandler(ReporterHandlerBase): """class handling std output""" def detail_view(): ... def string_view(): ... ``` ### `OutputHandler` The second abstract base class will concern itself with output. We want to do this because we would eventually like to have a plugin hook that will allow developers add arbitrary output handlers. This class will be named `OutputHandler` and will contain the following abstract methods: - `render` - Takes in a string and *renders* to the chosen output stream #### Example code ```python from abc import ABC, abstractmethod import sys class OutputHandlerBase(ABC): """ Base class for all output handlers """ @property @abstractmethod def name(self) -> str: """ Name of the output handler; this will be how this output handler is referenced in configuration files """ ... @abstractmethod def render(self, output: str) -> None: """Render implementation goes here""" ... class StdoutHandler(OutputHandlerBase): """ Handles writing output strings to stdout """ @property def name(self) -> str: return "stdout" def render(self, output: str) -> None: sys.stdout.write(output) ``` ### Where does the configuration come from? ```python reporters = ( {"backend": "stdout", "output": "stdout"}, {"backend": "json", "output": "file"}, ) ```