Pre-PEP: `textfmt module`: A Standardized Approach to Terminal Text Styling

Lets say we don’t use _colorize

I would be happy to make a full-fleged module complete with tests designed as a public API

It could include colors, word art, tables etc. all in the console

We could start with colors and if it gets enough community support, we can add everything else

As @timhoffm said, having a simple light stdlib that provides features like this doesn’t do any harm to us; having a minimum python version is much better than a 3rd party package which can make things ever-so complicated for programs optimized for simplicity and performance.

As for using _colorize: We don’t want to break working code. Lets make this a new library similar to _colorize but optimized to be a public API.

Same problem with colorama and termcolor: Third party libraries add an extra level of complexity for programs that don’t need complexity.

Define the color constants as class attributes on ANSIColors , and then make a ColorCodes set? Would an Enum be better? Is it ok that NoColors = ColorCodes() ? Theming is a complex topic. Are you confident that the implemented approach is reasonable?

Yes. I am certain. It is quite reasonable; so many programs would benefit from this and this such as small yet important feature it deserves to have its own module. (IMO)

Sorry, but waste of what?

For simple programs that benefit from this no one wants to download a whole formatting library

Why not? Computers are fast. Why learn two libraries?

Working in a venv? Publishing a PyPi package? Oftentimes using the stdlib is way more convenient and not everyone wants to bundle and deal with reliability from 3rd party modules. If people want basic yet working support, they use stdlib. For more complex needs they can use rich.

I couldn’t have said it better myself:

Most people don’t actually need the bells and whistles provided by heavyweights such as Rich. The basic features offered by _colorize is enough for most people who just want to be able to format important words in bold or red.

And finally in response to:

Promoting a private module to public brings with it far greater maintenance obligations that you’re asking the core developers to take on. Beyond this, and the obvious other considerations of locking in the API, it means that there is less incentive to innovate, as the stdlib option will become the default, even if other packages have better solutions.

I would deal with the issues/PRs and everything else. And we want the stdlib to give a simple easy and functional example for simple programs that could take them to the next level

TL;DR

We should give an option as a new textfmt module in the stdlib for simplicity and basic support, while if people need more they can use heavyweights. By doing this we would take many python applications to the next level!

Yes, pretty much this.

We started adding colour to the stdlib in 3.13 for tracebacks and doctest, and in 3.14 for syntax highlighting in the REPL, and for unittest, argparse, and json CLIs.

We’ve tried hard to pick colours that work well in dark and light mode, that have good contrast, and are also still readable for colour blind people.

However, there’s a million different terminals out there with a million different configs, and we want to make sure people have the possibility to adjust those colours.

Rather than just monkeypatching individual colours, so for example you get a different shade of red used across the board, we’ve added experimental theming support so you can tailor the colours as you wish. For example, you can adjust the colours used by the REPL for numbers and comments, and not change the yellow and red in unittest.

Time was short to come up with a public API for 3.14, so it’s in the internal _colorize module. We’d like you to play with it and give feedback, and we’ll look into coming with something public for 3.15. Whether this is only the theming API, or also general colour stuff, is to be decided.

As it’s internal, the documentation is currently in the docstring: Lib/_colorize.py#L78-L114.

3 Likes

I’ll look into it.

But, before we continue, I want to make sure that we collectively agree that this is a good idea.

Also, I was thinking that we could take it a step further with this proposed textfmt module with things like word art, tables, etc. all have to due with text formatting

I was thinking this as a mashup of different console-styling related things with color being a key feature

I think it’s premature to write a PEP at this point and I’m surprised you didn’t reach out to anybody actually involved in implementing this functionality in the standard library. With all due respect, while anybody can suggest a PEP, it’s less effective coming from a person with no skin in the game.

The way we’re approaching this for 3.14 is to:

  • organize text-based color usage within the standard library around theming in _colorize
  • softly suggest to users theming is supported via the experimental _colorize API

Once we get feedback from users, I will write a PEP and expose the module in 3.15.

8 Likes

For example a basic script I put together in two minutes

class TextFmt:
    """ ANSI Escape Code Formatting for Python CLI Output """
    RESET = "\033[0m"
    BOLD = "\033[1m"
    UNDERLINE = "\033[4m"
    
    # Foreground Colors
    COLORS = {
        "red": "\033[31m",
        "green": "\033[32m",
        "yellow": "\033[33m",
        "blue": "\033[34m",
        "magenta": "\033[35m",
        "cyan": "\033[36m",
        "white": "\033[37m",
    }
    
    # Background Colors
    BG_COLORS = {
        "red": "\033[41m",
        "green": "\033[42m",
        "yellow": "\033[43m",
        "blue": "\033[44m",
        "magenta": "\033[45m",
        "cyan": "\033[46m",
        "white": "\033[47m",
    }

    @staticmethod
    def style(text, *styles):
        """ Applies ANSI styles to text """
        return "".join(styles) + text + TextFmt.RESET

    @staticmethod
    def banner(text, char="█", width=50):
        """ Customizable Word Art Banner """
        top = char * width
        middle = f"{char} {text.center(width - 4)} {char}"
        return f"{top}\n{middle}\n{top}"

    @staticmethod
    def table(headers, data):
        """ Formats a table structure for CLI output """
        col_widths = [max(len(str(item)) for item in col) for col in zip(headers, *data)]
        border = "+".join("-" * (w + 2) for w in col_widths)

        def format_row(row):
            return "| " + " | ".join(f"{str(item).ljust(w)}" for item, w in zip(row, col_widths)) + " |"

        return f"{border}\n{format_row(headers)}\n{border}\n" + "\n".join(format_row(row) for row in data) + f"\n{border}"

# Example Usage
if __name__ == "__main__":
    fmt = TextFmt()

    print(fmt.style("Hello, World!", fmt.BOLD, fmt.COLORS["blue"]))
    print(fmt.banner("TEXTFMT", char="*", width=40))

    headers = ["Name", "Color", "Effect"]
    data = [
        ["Bold", "White", "Strong emphasis"],
        ["Underline", "Blue", "Text decoration"],
        ["Warning", "Yellow", "Cautionary message"],
    ]

    print(fmt.table(headers, data))

While this isn’t a module rather a demo script, we could use this principle while shaping.

And no, I was not planning to write a PEP by that comment. We can eventually. That comment was to help us see a clear upvote/downvote idea in the community so we can start focusing on how we could do this.

You could have spent those two minutes looking at what’s in the _colorize module.

1 Like

This may be aiming too high. Word art is IMHO not an essential feature suitable for the standard library. Tables might or might not be (I tend towards not), anyway, it’s technically completely unrelated to coloring and therefore should be discussed separately. Generally it helps the discussion to keep the topic focussed. - Edit: Rereading your initial post, I get the feeling that it’s a major point in your proposal to have a very powerfull text formatting library. To that I’d say that’s out of scope for the standard library.


Anyway, glad to see there are ambitions to look into a potentially public coloring more closely for 3.15.

1 Like

Fair. That was just an example of what we could do. Right now we are discussing coloring, but IMO we could design this so that if we want to add something we don’t feel restricted to only colors. Because of this I propose to do something like:

Expose colorize as textfmt: eg. reuse all the _colorize.py code in our public library so we can have it so that we don’t change any backend code yet still use working code.

I love your sense of humor

@ambv
Thanks for the offer! Would it be okay if I were to write the PEP (eventually) and you kinda like mentor me? I really wanted this to be my first PEP.

It would make my computer science teacher proud of me :slight_smile:

And we would be reusing a lot from _colorize.py

Reading it now, we would be stealing a bunch of things like

  • ANSIcolors
  • Argparse
  • theme
  • decolor
  • can_colorize
  • get_theme

And remodeling these to be better for a public api while not actually modifying _colorize.py for 2 reasons:

  • Full control to edit our new library without breaking anything in stdlib
  • Not having to preserve compatibility with existing stdlib that uses _colorize.py.

We would essentially be marketing textfmt’s early stages as a improved version of _colorize.py

What do 'yall think?

I told you this above but maybe explicit is better than implicit. It’s premature to do this right now and I’m not confident you should be the person driving this, Rihaan. Given your very young age it’s impressive that you’re interested in this and I appreciate your enthusiasm, but contributing to Python shouldn’t start with PEPs. Implement your ideas and put them on PyPI first.

8 Likes

@sharktide I acknowledge your motivation and dedication. I read from your GitHub profile that you are relatively new to the trade. Adding to CPython is quite high on the difficulty scale since it so fundamental and widely used. That requires not only very good language understanding and an idea, but also careful design, ideas of scope, a sense of what not to do, evaluating different alternatives, judgement of flexibility, maintainability, and utility, etc. Writing some new library is easy, writing the right new library (or deciding not to write it) is an art and requires substantial experience. With all due respect, writing a PEP as one of the first major open source contributions is likely not going to work well. I recommend to gather experience with smaller contributions to existing projects (in particular also python libraries and not necessarily cpython), or if you are very focussed on textfmt, but a third party library.

2 Likes

Biggest python package speedrun of my life:

pip install textfmt

And my github profile does not necessarily reflect me. I may have joined github in november, but that doesn’t mean I am clueless.

I have worked with TensorFlow, made many-a pypi package (TensorFlowTools, reStructuredPython, now this one) and have been coding with python since I was 8.

I get it, but I want some credit too. If this even gets to that stage, I’ll write a PEP draft anyway, post it in this convo and pass it on to you guys from there.

Note that the python package I made is again very basic and has no official documentation. Just a plausible working example of what it could be.

Anyway, that’s off topic.

I agree that word art if not essential to this proposed library and same with Tables. They would be a nice touch but not required,

Bringing it back to simplicity, we could as well as having a whole class and styleing function. (Like in my package), we could provide that as well as just a simple class like

class EasyColors:
    HEADER = '\033[95m'
    BLUE = '\033[94m'
    CYAN = '\033[96m'
    GREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'
    # and so on

Having this and a proper class with more colors and .style attr might be better for simplicity in certain scenarios. Like with both someone could do

print(f"{EasyColors.GREEN}Something worked!{EasyColors.ENDC}")

or

print(TextFormat.style("Something Worked", TextFormat.COLORS["green"]))

What do we think about this?

Frankly, I think this is premature. - No offense meant. You are definitely very far for your age, but there‘s also still a lot to learn. I‘m taking the time to respond because I feel you have potential. This is not to discourage you, but to give you more perspective and a chance to grow.

I have not seen any of your other work, so will comment only on what I‘ve seen in this thread:

  • Cranking out a proof-of-concept to PyPI is not what I’d expect from an experienced developer. Just a GitHub project would be sufficient and better. PyPI names are limited. If you are not reasonably sure that you will develop and maintain such a library, this just creates noise on PyPI and blocks others from later using that name.
  • Also you can’t have done serious design in half an hour. But that would be the important stuff. Putting “some code” on PyPI is not relevant for this discussion. Instead it’s finding out what the “right code” is.
    I‘d expect requirements, design decisions with explanation, discarded alternatives. See e.g. Theming support for _colorize · Issue #133346 · python/cpython · GitHub, in particular the original comment. If you’d written something like that, I’d be much more impressed than by a package on PyPI.
  • Expect things to take time. As written by @ambv above they’ll plan to look further into this for 3.15. That is fast in terms of CPython development. Slower responses with more considered ideas are more valuable than quick replies with your next thought.
  • Credit should not be a major motivation for contributing. We are here to improve the language (and I genuinely believe you are too). But that’s a long and elaborate process. “I want credit for my contribution” does not help here, because it will tempt you to push for “your solution”. But this is about finding the right solution collaboratively. If you do continuous valuable contributions, credit and reputation will come over time, but don’t expect every single contribution to be appropriately valued.
8 Likes

Now you’re just making assumptions about me, and I am getting annoyed.

  1. I am actually going to mantain this, otherwise I wouldn’t have published it to PyPI, because as you stated package names are limited.

  2. No, I did not do serious design in 30 minutes. There is a reason why it was 0.1.0

  3. I did not include design decisions with explanation and discarded alternatives for the same reason. v0.1.0 does not contain a full-fleged library. Expect more when it comes to v1.0.0

  4. I know things will take time

  5. Credit was not my motivation for this. At this point I am just getting pissed. And, no I do not care if my solution is implemented as long as we can actually do something.

By now I don’t care. I’ll only reply if it is programming related.

To be very clear: This was supposed to be about Terminal Text Styling and we turned this into a firefight. Let’s keep it friendly and on-topic

I am sorry if I have annoyed you. That was not my intention. I have only tried to give feedback on how I perceived the approach to the topic.

I will withdraw from the topic. All I could contribute, both on content and approach has been said.

Thanks for keeping the response friendly and topic-focused, although my wording annoyed you.

1 Like

@ZeroIntensity @Wulian233 @ambv @picnixz @hugovk @pf_moore

Any other suggestions or ideas for this?

Also, could someone with appropriate rights unlock this topic from the 8hr cooldown?