Catalog PyPI
Back to packageRELEASE DETAILS

1.0.0

PyPI

2ning

Colorful and customizable logging module for CLI applications.

cliloggingrichyaml
Published7 May 2026
Direct dependencies20
Transitive dependenciesNot calculated

README

Version 1.0.0 documentation

Tune your logs

tuning icon

tuning is a colorful and customizable dual-logging module for CLI applications. Emojis included 😉.

The idea is simple, display human-readable and colorful terminal logs, while keeping file logs detailed and sober.

It builds on stdlib logging and Rich, so you can use it as usual:

  • logging via named functions: logger.my_custom_lvl()
  • natural language defined styles: bold italic white on red

Features

Easy to use

  • Stdlib-like root logger configuration through basicConfig(), including rotating logs.
  • YAML-driven configuration (we got your back with the defaults)

Fully customizable

  • Logging levels: icon, symbol, and style. (already added TRACE and SUCCESS)
  • Colorful console output, with optional information: timestamp and path (caller file).
  • Styled interactive prompts, with default value: logger.prompt(...)
  • Custom app banners through banner(...)
  • Inside-of-the-box: experimental display mode which encloses console records into boxes. Beautiful, but

Optional extras are available for CLI and TUI integrations, but tuning does not wrap Typer or Textual.

Install

pip install 2ning

For local development:

git clone https://github.com/octanima-labs/tuning
cd tuning
pip install -e ".[dev,cli,tui,docs]"

Quick Start

import tuning

logger = tuning.getLogger(__name__)

logger.info("application started")
logger.success("everything looks good")

name = logger.prompt("Your name?")
logger.info("hello %s", name)

This zero-config path installs packaged console defaults lazily on first use. Configure logging explicitly when you want predictable startup behavior:

import tuning

tuning.basicConfig(
    level="INFO",
    show_time=True,
    datefmt=tuning.ISO_FORMAT,
)

logger = tuning.getLogger(__name__)
logger.info("configured output")

Use show_level=False when console output should contain only the message.

Add a runtime-only custom level:

tuning.addLevel(7, "MY_CUSTOM_LEVEL", symbol="MC", style="bright_blue")

logger = tuning.getLogger(__name__)
logger.my_custom_level("custom runtime output")

Print a custom app banner from banners.txt:

import tuning

tuning.banner()

With no path, banner() searches upward from the calling file for banners.txt. Each section starts with a strict lowercase marker such as ### ansi_shadow.

Customize rendering with Rich styles:

tuning.banner(
    name="ansi_shadow",
    border_style="bright_cyan",
    text_style="bold bright_magenta",
    background_style="on black",
    padding=(0, 2),
    box="HEAVY",
)

Files And YAML

Use a file handler:

tuning.basicConfig(filename="app.log", level="INFO")

Use rotating file logs:

tuning.basicConfig(
    filename="app.log",
    max_bytes="10 MB",
    backup_count=5,
)

Export a full starter YAML config into your project:

import tuning

config_path = tuning.export()

Load YAML configuration:

tuning.basicConfigFromYaml("tuning.yml", force=True)

Documentation

Development

Common contributor commands and documentation build steps live in Development.

The short version:

./.venv/bin/python -m pytest
./.venv/bin/ruff check .
./.venv/bin/mypy tuning tests
./.venv/bin/sphinx-build -W -b dirhtml docs site

License

tuning is distributed under the MIT license.


We 💙 CLI

tuning icon

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Declared dependencies 20

Native requirements
20 direct targetsNot calculated transitive count
Package / targetRequirementRelationship
bitmath
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
bitmath>=2.0.1runtime
PyYAML
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
PyYAML>=6.0.3runtime
rich
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
rich>=15.0.0runtime
build
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
build>=1.3.0; extra == "dev"runtime
mypy
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
mypy>=1.18.2; extra == "dev"runtime
pre-commit
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pre-commit>=4.5.0; extra == "dev"runtime
pytest
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pytest>=9.0.3; extra == "dev"runtime
ruff
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
ruff>=0.14.9; extra == "dev"runtime
setuptools
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
setuptools>=77; extra == "dev"runtime
twine
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
twine>=6.1.0; extra == "dev"runtime
types-PyYAML
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
types-PyYAML>=6.0.12.20250915; extra == "dev"runtime
wheel
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
wheel>=0.45.1; extra == "dev"runtime
myst-parser
Environment
{
  "extras": "",
  "marker": "extra == \"docs\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
myst-parser>=4; extra == "docs"runtime
pydata-sphinx-theme
Environment
{
  "extras": "",
  "marker": "extra == \"docs\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pydata-sphinx-theme>=0.16; extra == "docs"runtime
sphinx
Environment
{
  "extras": "",
  "marker": "extra == \"docs\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
sphinx>=8; extra == "docs"runtime
sphinx-autodoc-typehints
Environment
{
  "extras": "",
  "marker": "extra == \"docs\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
sphinx-autodoc-typehints>=3; extra == "docs"runtime
sphinx-copybutton
Environment
{
  "extras": "",
  "marker": "extra == \"docs\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
sphinx-copybutton>=0.5; extra == "docs"runtime
sphinx-design
Environment
{
  "extras": "",
  "marker": "extra == \"docs\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
sphinx-design>=0.6; extra == "docs"runtime
typer
Environment
{
  "extras": "",
  "marker": "extra == \"cli\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
typer>=0.25.1; extra == "cli"runtime
textual
Environment
{
  "extras": "",
  "marker": "extra == \"tui\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
textual>=8.2.5; extra == "tui"runtime

Distribution files

2 collected
2ning-1.0.0-py3-none-any.whl

bdist_wheel · 21.66 KB · Python py3

Source file
2ning-1.0.0.tar.gz

sdist · 29.15 KB · Python source

Source file

Repository

PENDING
octanima-labs/tuning

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 15:26 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package_version:ac31320b5b08a4d83e8332c63c67a8efeff188b4e83f95a6a3e3193073d175cb

Other collected versions

View all collected versions →