$ a personal python utility library
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.
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})
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.")
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
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.