Background

python utilities

$ a personal python utility library

01.

Overview

A small personal Python utility library containing code I frequently use across my personal projects. Rather than rewriting the same helpers every time, this library packages them up for easy reuse and installation directly from GitHub.

The library includes an enhanced logging system with colored output and per-call overrides, individual logger instances for modular apps, and a handful of general-purpose utilities for environment variables and HTTP responses.

Licensed under the MIT License and installable directly from GitHub via pip.

Technologies Used

  • Python

Links

02.

Clogger

Clogger is a static logger with colored output, timestamps, source tracking, and per-call overrides. Use it as a drop-in replacement for print() with a lot more control.

from pyutils import Clogger, CloggerConfig, CloggerColor, CloggerSetting, CloggerOverrideFactory, LogLevel

Clogger.info("Server started.")
Clogger.warn("High memory usage.")
Clogger.error("Failed to connect.", exc=KeyError)  # logs then raises
Clogger.debug("Request payload: {...}")
Clogger.action("User clicked submit.")
Clogger.log("BOOT", "Config loaded.")              # custom tag

Adjust global behavior by swapping the config:

Clogger.config = CloggerConfig(min_log_level=LogLevel.WARN)
Clogger.config = CloggerConfig(disable_colors=True, use_tag=False)
Clogger.config = CloggerConfig(print_disabled=True, write_to_file=True, log_file_path="run.log")
Clogger.config = CloggerConfig()  # reset

Per-call overrides let you deviate from the global config for a single line:

Clogger.info("Verbose trace.", settings_override=CloggerOverrideFactory.verbose())
Clogger.error("Saved quietly.", settings_override=CloggerOverrideFactory.file_only("errors.log"))
Clogger.debug("Dict dump.", settings_override=CloggerOverrideFactory.pretty())

# Combine overrides — later keys win on conflict
Clogger.info("Verbose and saved.", settings_override=CloggerOverrideFactory.combine(
    CloggerOverrideFactory.verbose(),
    CloggerOverrideFactory.write_to_file("verbose.log"),
    {CloggerSetting.DISABLE_COLORS: True},
))

Create reusable custom loggers with make_log:

log_boot = Clogger.make_log("BOOT", color=CloggerColor.CYAN, level=LogLevel.INFO)
log_boot("App starting up.")   # [BOOT]    | main.py:12 | App starting up.

# Bake in a settings baseline — still overridable per call
log_trace = Clogger.make_log(
    "TRACE",
    color=CloggerColor.MAGENTA,
    settings_override={CloggerSetting.SIMPLIFY_TIMESTAMPS: False},
)
log_trace("Deep trace.")
log_trace("No source.", settings_override={CloggerSetting.SHOW_SOURCE_FILE: False})
03.

Clogobj

Clogobj provides individual logger instances with their own baked-in config and optional name. Useful when different parts of your app need different logging behavior without touching the global Clogger.config.

from pyutils import Clogobj, ClogobjFactory, CloggerConfig

verbose_logger = ClogobjFactory.verbose()
silent_logger  = ClogobjFactory.silent()   # useful in tests
file_logger    = ClogobjFactory.file_only("run.log")

verbose_logger.info("Full timestamps and source shown.")
silent_logger.error("No output at all.")
file_logger.warn("Written to run.log only.")

Name a logger to a module — the name appears before the level tag on every line:

db_logger = ClogobjFactory.for_module("Database")
db_logger.info("Connection established.")   # [Database][INFO]  | db.py:42 | Connection established.
db_logger.warn("Slow query detected.")      # [Database][WARN]  | db.py:43 | Slow query detected.

make_log on an instance returns a bound logger that inherits the instance's settings and name:

auth = Clogobj(
    settings=CloggerConfig(simplify_timestamps=False),
    name="Auth",
)

log_token = auth.make_log("TOKEN", color=CloggerColor.YELLOW, level=LogLevel.WARN)
log_token("Invalid token.")   # 2025-01-01 17:58:40 EST [Auth][TOKEN]  | auth.py:7 | Invalid token.

# Per-call overrides still layer on top
log_token("Suppressed.", settings_override={CloggerSetting.PRINT_DISABLED: True})

Use log_errors as a decorator to automatically route exceptions through the instance:

db = ClogobjFactory.for_module("Database")

@db.log_errors
def connect():
    raise ConnectionError("timeout")

connect()  # [Database][ERROR] | db.py:8 | connect failed: timeout

Write a component's logs to its own file:

db_logger = ClogobjFactory.for_file("db.log", also_print=True)
db_logger.info("Printed to terminal and saved to db.log.")

Fully custom when nothing else fits:

boot = ClogobjFactory.custom(
    settings=CloggerConfig(simplify_timestamps=False, show_source_file=True),
    name="BOOT",
)
boot.info("App starting up.")
boot.log("INIT", "Config loaded.")
04.

Option & Result

Rust-inspired types for explicit null and error handling — no bare None checks or scattered try/except blocks.

Option wraps a value that may or may not exist. Unlike bare None, Some(None) is a valid distinct state.

from pyutils import Option

Option.some(42).unwrap()           # 42
Option.none().unwrap_or(0)         # 0
Option.none().unwrap_or_else(fn)   # fn() — only called if None

Result wraps either a success value or an error. Ok(None) and Err(None) are both valid and distinguishable.

from pyutils import Result

Result.ok(42).unwrap()                 # 42
Result.err("oops").unwrap_err()        # "oops"
Result.err("oops").unwrap_or(0)        # 0
Result.err("oops").expect("failed")    # raises: ValueError: failed: 'oops'

Both types support bool() coercion, equality, hashing, and iter unpacking:

bool(Option.some(0))     # True  — state-based, not value-based
bool(Option.none())      # False
bool(Result.ok(None))    # True
bool(Result.err("x"))    # False

Option.some(1) == Option.some(1)   # True
(value,) = Option.some("hello")    # unpacking
05.

Other Utilities

The library also includes two lightweight helpers for common tasks: safely loading environment variables and validating HTTP responses.

from pyutils import get_env, check_response
import requests

API_KEY = get_env("API_KEY")

response = requests.get("https://api.example.com/data")
if check_response(response):
    data = response.json()

get_env() wraps os.environ access with clean error handling so missing variables surface clearly rather than causing cryptic failures later in your code.

check_response() performs basic validation on a requests.Response object, covering common status codes and making HTTP error handling more concise.