Basic functionality for Python projects: logging setup, a Base class with built-in logger, validation utilities, and exception logging helpers.
pip install visionscaper-pybaseCall setup_logging() at application startup to configure the root logger:
from basics.logging import setup_logging, get_logger
setup_logging()
logger = get_logger('my_module')
logger.info('Application started')Output:
20260209-151557.123 INFO: my_module::<module>: Application started
Use get_logger() to create named loggers. The default log level is DEBUG, but can be overridden:
from basics.logging import get_logger
# Module-level logger (common pattern)
module_logger = get_logger(__name__)
# Logger with custom log level
import logging
quiet_logger = get_logger('noisy_lib', log_level=logging.WARNING)The default format includes a timestamp prefix:
YYYYMMDD-HHMMSS.msecs LEVEL: name::funcName: message
Example:
20260209-151557.123 INFO: train::main: Starting training
20260209-151557.456 WARNING: train::main: Low memory
| Variable | Default | Description |
|---|---|---|
PYBASE_LOG_PID |
unset | Set to 1 to prefix log lines with the process ID ([PID]) |
PYBASE_NO_TIMESTAMP_LOG |
unset | Set to 1 to disable the timestamp prefix |
PYBASE_SUPPRESS_LEGACY_WARNINGS |
unset | Set to 1 to suppress the deprecation warning about auto-configuration |
Example with PID enabled:
[4521] 20260209-151557.123 INFO: train::main: Starting training
You can pass a custom format string and date format to setup_logging():
from basics.logging import setup_logging
setup_logging(
format_string='%(asctime)s %(levelname)s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
)Use build_logging_format() to programmatically construct format strings with optional timestamp and PID, independent of environment variables:
from basics.logging import build_logging_format
fmt = build_logging_format(include_timestamp=True, include_pid=True)Importing basics.logging currently configures the root logger automatically via logging.basicConfig(). This ensures existing code continues to work, but will be removed in a future version. New code should call setup_logging() explicitly.
Set PYBASE_SUPPRESS_LEGACY_WARNINGS=1 to suppress the deprecation warning.
The Base class provides a built-in logger for any class that inherits from it:
from basics.base import Base
class MyProcessor(Base):
def __init__(self, name, **kwargs):
super().__init__(**kwargs)
self._log.info(f'Processor created: {name}')
def process(self, data):
self._log.debug(f'Processing {len(data)} items')
return [item * 2 for item in data]
proc = MyProcessor('example')
proc.process([1, 2, 3])Output:
20260209-151557.123 INFO: MyProcessor::__init__: Processor created: example
20260209-151557.124 DEBUG: MyProcessor::process: Processing 3 items
The logger name defaults to the class name. Override it with pybase_logger_name:
proc = MyProcessor('example', pybase_logger_name='custom_name')
# Logs will show: custom_name::__init__: ...Subclasses can override _pybase_get_logger_name() to customize the logger name dynamically, or _get_logger() to use a different logger implementation entirely:
class MyProcessor(Base):
def __init__(self, name, **kwargs):
self._name = name
super().__init__(**kwargs)
def _pybase_get_logger_name(self):
return f'MyProcessor[{self._name}]'Log an exception with message, type, and traceback as a single atomic log record:
from basics.logging import get_logger
from basics.logging_utils import log_exception
logger = get_logger(__name__)
try:
result = 1 / 0
except Exception as e:
log_exception(logger, 'Division failed', e)summarize_exception_chain() produces a readable summary of chained exceptions:
from basics.logging_utils import summarize_exception_chain
try:
try:
raise ConnectionError('timeout')
except ConnectionError as ce:
raise ValueError('failed to process request') from ce
except Exception as e:
print(summarize_exception_chain(e))Output:
* ValueError: failed to process request
* ConnectionError: timeout
For a single exception (no chain), it returns a simple one-liner:
KeyError: 'missing_key'
Use include_traceback=True to append the full traceback.
Get the class and message for each exception in the chain as a list of tuples:
from basics.logging_utils import get_all_exception_info
try:
...
except Exception as e:
for cls, message in get_all_exception_info(e):
print(f'{cls.__name__}: {message}')Type-checking helpers available from basics.base_utils or basics.validation_utils:
from basics.base_utils import is_number, is_dict, is_sequence, is_callable
is_number(42) # True
is_dict({'a': 1}) # True
is_sequence([1, 2, 3]) # True
is_callable(print) # True| Function | Description |
|---|---|
is_number(n) |
Instance of numbers.Number |
is_mapping(d) |
Instance of collections.abc.Mapping |
is_dict(d) |
Instance of dict |
is_bool(d) |
Instance of bool |
is_class(c) |
Instance of type |
is_sequence(l) |
Instance of collections.abc.Sequence |
is_non_empty_string(s) |
str with length > 0 |
is_file(fname) |
os.path.isfile() check |
is_callable(func) |
callable() check |
is_int_sequence(seq) |
Sequence where all elements are numbers |
sequences_are_compatible(a, b) |
Two sequences with equal length |
MIT License. See LICENSE for details.