Even though I don't like C's do everything yourself, I love to reinvent the wheel for minor things.

This snippet I put here for myself, but you're encouraged to use it.

The loop below the class is to make Colors's be at the same time non-instantiated and have all the colors dynamically assigned from the dictionary.

Colors.color-lowercase are methods for wrapping a string as color + string + reset, and Colors.color-uppercase are corresponding color codes as strings.

 1class Colors:
 2    colors: dict[str, str] =  {
 3        "black": "\033[30m",
 4        "grey": "\033[90m",
 5        "white": "\033[37m",
 6        
 7        "red": "\033[91m",
 8        "green": "\033[92m",
 9        "yellow": "\033[93m",
10
11        "blue": "\033[34m",
12        "magenta": "\033[35m",
13        "cyan": "\033[36m",
14
15        "bold": "\033[1m",
16        "italic": "\033[3m",
17        "underline": "\033[4m",
18
19        "reset": "\033[0m"
20    }
21
22for name in Colors.colors:
23    setattr(Colors, name.upper(), Colors.colors[name])
24
25    if name != "reset":
26        def color(cls, arg, name = name):
27            return Colors.colors[name] + arg + Colors.colors["reset"]
28
29        setattr(Colors, name.lower(), classmethod(color))