A library for optimistically and explicitly generating and printing ANSI color and style codes.
Project description
niji
From Japanese 虹 niji, meaning "rainbow".
The purpose of this package is to provide coloration of text in the terminal. It defaults behaviour to truecolor/24bit ( full RGB) color, but can gracefully downgrade when this is not supported by the terminal.
The two main functions provided are:
colored(text, *, fg, bg, styles, mode=TRUECOLOR) -> str, which returns a string with the necessary ANSI codes injected for later manipulation and printing.cprint(text, *, fg, bg, styles, mode=AUTO, file=sys.stdout) -> None, which is a convenience wrapper aroundprint(colored(...)).
Examples
from niji import colored, cprint, TextStyle
# use `colored` to style text, getting a string back
# this will always use truecolor sequences unless overridden
green_text = colored("some green text", fg=(0, 100, 0))
blue_on_purple = colored("blue text on purple background", fg=(0, 0, 100), bg=(100, 0, 100))
italic_bold_red = colored("colored also takes hex colors", fg="#aa0000", styles=TextStyle.ITALIC | TextStyle.BOLD)
close_enough = colored("use a color that's as close as a 256-color terminal can use", fg="#AE03B8",
mode=ColorMode.EXTENDED_256)
# use `cprint` when you want to print directly
# it will automatically handle the ColorMode
cprint("let's do... pink this time", fg="#C05ADC", styles=TextStyle.UNDERLINE)
with open("/path/to/some/file", encoding="utf-8") as f:
cprint("you can write to a file, and colors will be suppressed", fg=(17, 12, 14), file=f)
cprint("even on a 256-color term, this will use the truecolor codes", fg=(0, 255, 0), mode=ColorMode.TRUE_COLOR)
# use `aware_colored` for a mix of the two
with open("/path/to/some/file", encoding="utf-8") as f:
store_for_later = aware_colored("blue on red... except it'll be plain text", fg="#0000FF", bg="#FF0000", file=f)
f.write("normal text" + store_for_later)
Why niji instead of termcolor?
The main benefit is that niji strives to be more optimistic and explicit about its settings. Truthfully, I've used
termcolor for a long time and rarely had issues with it. But I had a situation like
# works just fine, producing the blue text
$ python -c "from termcolor import cprint; cprint('some text', '#0000aa')"
# doesn't work (no color output) even though less -R supports it
$ python -c "from termcolor import cprint; cprint('some text', '#0000aa')" | less -R
which I expected to maintain the colors. I, as the person writing the code, knew that the colors were safe to emit, and
termcolor was adhering to the standard that colors should not be automatically written through a pipe (a convention I
wasn't aware of). I'd previously used ripgrep in a similar fashion, but rg --color=always is an easy way around
that. termcolor doesn't seem to support the same level of override (without setting an environment variable).
So niji aims to provide the user the explicit option to say "hey, I always want you to output color, regardless of
context". And thus the above example could be
# does emit colors properly
# (I know I'm using a truecolor terminal, but I could change the mode to EXTENDED_256 if I wasn't)
$ python -c "from niji import cprint, ColorMode; cprint('some text', fg='#0000aa', mode=ColorMode.TRUE_COLOR)" | less -R
# this also works and is a bit shorter, relying on the default mode value for colored since I know truecolor is safe
$ python -c "from niji import colored; print(colored('some text', fg='#0000aa'))"
Installation
This library can easily be installed by any Python package manager and does not incur any other external dependencies.
$ pip install niji
# to test installation (#FF000 should be supported regardless of terminal)
$ python -c "from niji import cprint; cprint('a red hello world!', fg='#FF0000')"
Alternatively, using uv as a different example, the above two commands are:
$ uv add niji
$ uv run python -c "from niji import cprint; cprint('a red hello world!', fg='#FF0000')"
Supported Python Versions
All versions of Python from 3.10 onward are supported. At the time of development (late 2025), 3.9 has been declared end-of-life.
Known Limitations
- Hex color parsing does not support shorthand codes (such as
#ABCto mean#AABBCC.) TextStylecombinations (such asTextStyle.ITALIC | TextStyle.UNDERLINE) is known to produce type warnings in PyCharm. In this case, the warning isUnexpected type(s): (Literal[TextStyle.UNDERLINE]) Possible type(s): (Literal[TextStyle.ITALIC]) (Literal[TextStyle.ITALIC]). This is a limitation within PyCharm's handling of standard libraryenum.Flag. These warnings are not raised by mypy or Pyright/Pylance and are safe to ignore.
Documentation
ColorMode
niji supports injecting various versions of ANSI codes, depending on the capabilities of the terminal emulator. Some,
like kitty, support full 24bit RGB colors, while others, like konsole, only support the 256 indexed colors.
ColorMode is what controls which version of the codes is injected.
ColorMode is an enum with the following values:
ColorMode.TRUE_COLOR: denotes the usage of full 24-bit "truecolor" codes. In this mode, colors will be rendered exactly as provided. This is the default value passed tocolored.ColorMode.EXTENDED_256: denotes the usage of the full 8-bit (256 color) block of colors. This is a common minimum standard for modern terminal emulators.ColorMode.STANDARD_16: denotes the usage of the minimal 4-bit (16 color) block of colors (black, red, etc.) and their bright versions.ColorMode.NONE: denotes the omission of colors. This might be rare to set manually, but it provides an ability to omit colors when necessary (such as when writing to a file).ColorMode.AUTO: denotes auto-detection of available capabilities. This is only supported bycprintandaware_colored, as they have information about where the string is being written.
With ColorMode.EXTENDED_256 and ColorMode.STANDARD_16, any truecolor colors passed to colored, aware_colored,
and cprint will be gracefully downgraded to the nearest indexed color, providing an "as good as possible" rendering.
Note that colored(...) takes ColorMode.TRUE_COLOR as an optimistic default. This is because the purpose of colored
is to designed to return a string that formats the text according to the given styles, and truecolor guarantees doing
that with optimal fidelity.
By contrast, aware_colored(...) and cprint(...) use ColorMode.AUTO as their default since their priority is to
more conservatively render the text as well as they can, given the constraints of the terminal (their file parameter).
TextStyle
TextStyle is an enum flag that allows for additional formatting styles on top of colors. The full list of members are
TextStyle.NONE, TextStyle.BOLD, TextStyle.DIM, TextStyle.ITALIC, TextStyle.UNDERLINE, TextStyle.BLINK,
TextStyle.REVERSE (swap fg and bg colors), TextStyle.CONCEALED (hide text), TextStyle.STRIKEOUT.
Because this is an enum.Flag, these can be combined via | operations: TextStyle.DIM | TextStyle.STRIKEOUT will
render as both dim and strikeout text.
Note that TextStyle.NONE gets absorbed by this process: TextStyle.DIM | TextStyle.NONE is equivalent to just
TextStyle.DIM.
RGBColor
RGBColor is a thin typing.NamedTuple that has .red, .green, and .blue attributes. It is used internally for
representing different colors, but is provided to the user as a top-level name (from niji import RGBColor) for
convenience.
ColorInput
ColorInput is a type alias for int | str | tuple[int, int, int] | RGBColor | Sequence[int].
int: color is interpreted as an indexed color according to the 256 color mapstr: color is interpreted as a hex code (with or without a leading #). Shorthand codes are not supported.tuple[int, int, int] | RGBColor: color is interpreted as an (R, G, B) tuple. These values must be ints, not floats ( e.g.,(4.0, 5.0, 9.0)is invalid).Sequence[int]: same as above, provided separately to allow other containers (such aslist[int]). This must be a sequence of length 3.
colored vs aware_colored
These two functions have very similar signatures:
def colored(
text: str,
*,
fg: ColorInput | None = None,
bg: ColorInput | None = None,
styles: TextStyle | None = None,
mode: ColorMode = ColorMode.TRUE_COLOR
) -> str:
...
def aware_colored(
text: str,
*,
fg: ColorInput | None = None,
bg: ColorInput | None = None,
styles: TextStyle | None = None,
mode: ColorMode = ColorMode.AUTO,
file: TextIO = sys.stdout
) -> str:
...
The difference is that aware_colored accepts ColorMode.AUTO as a valid option (and it's the default), allowing it to
query the passed file parameter to determine what type of codes it should use. Note that it still doesn't write to the
file (use cprint for that).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file niji-0.1.0.tar.gz.
File metadata
- Download URL: niji-0.1.0.tar.gz
- Upload date:
- Size: 9.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.0rc2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b7785c9bccfcd338a61157f72a7e1ef7c1f73ee5075ae971ecdf52533ca3605c
|
|
| MD5 |
c12c48679fc9dd7af55bfa26e18966b6
|
|
| BLAKE2b-256 |
9eaa1e69dc230817f8a99d9b1b3610b85e6385cd08219706baadf82dd70b5c2a
|
File details
Details for the file niji-0.1.0-py3-none-any.whl.
File metadata
- Download URL: niji-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.0rc2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
72d8041a027bf69745cab2b6ec0a90fc63c1cdaf2fc61c84d10fb1e28fc54cf5
|
|
| MD5 |
16ca0f4ab20df2ccda50519edb4d5d9b
|
|
| BLAKE2b-256 |
5d71f7c9e4a9050b72b8e8207aa489b7f8d1212768ee7fd69f8358a2d95a1b3b
|