Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/alex-ber/AlexBerUtils/llms.txt

Use this file to discover all available pages before exploring further.

The alexber.utils.mains module provides utilities for common application startup tasks: fixing the working directory, loading .env files inside packed distributions, and protecting multiprocessing pools from unpicklable exceptions.

Working directory helpers

fixabscwd()

Changes the current working directory to the directory that contains the __main__ module. When you launch a Python script from a different directory, relative file paths (config files, assets, etc.) resolve against the shell’s working directory instead of the script’s location. fixabscwd() corrects this once at startup.
import alexber.utils.mains as mains

# Call once, early in your entry-point
mains.fixabscwd()

# Now relative paths resolve from the directory that contains __main__
with open('config/settings.yml') as f:
    ...
fixabscwd() is a no-op when running inside a REPL, an IPython notebook, or a frozen executable (sys.frozen == True).

FixRelCwd(relPackage, logger=None)

A context manager that temporarily changes the working directory to the location of an installed package, then restores the original directory on exit. Useful when you need to call a module that resolves relative paths against its own installation directory.
import myapp.worker as worker
from alexber.utils.mains import FixRelCwd

with FixRelCwd(worker) as directory:
    # CWD is now the directory where worker/__init__.py lives
    worker.run()  # relative paths inside worker resolve correctly

# CWD has been restored to its original value
ParameterTypeDescription
relPackagemoduleThe installed package whose directory will become the working directory.
loggerlogging.Logger | NoneLogger for debug output. Defaults to the module logger.

Worker process guard

GuardedWorkerException(logger=None, suppress=False, default_exc_message=...)

A context manager that prevents pool.join() from hanging indefinitely when a worker process raises an exception that cannot be pickled back to the parent. Many exception types (for example, subprocess.CalledProcessError which holds stdout/stderr buffers) are not picklable. When such an exception escapes a multiprocessing.Pool worker, the pool’s join call blocks forever. GuardedWorkerException catches the exception, logs it, and optionally re-raises a plain, picklable Exception in its place.
This is a known CPython bug that still exists in Python 3. Always wrap the body of Pool worker functions with this context manager.
from multiprocessing import Pool
from alexber.utils.mains import GuardedWorkerException
import logging

log = logging.getLogger(__name__)

def worker_task(item):
    with GuardedWorkerException(logger=log):
        # Any unpicklable exception raised here is replaced with a plain Exception
        result = subprocess.run(['my-tool', item], check=True, capture_output=True)
        return result.stdout

with Pool(4) as pool:
    pool.map(worker_task, items)
    pool.join()  # will no longer hang
ParameterTypeDefaultDescription
loggerlogging.Logger | NoneNoneLogger to record the caught exception. If None, traceback is written to stderr.
suppressboolFalseWhen True the caught exception is silently swallowed. When False a plain Exception is raised instead.
default_exc_messagestr"Worker failed"Message used when re-raising the replacement exception.

Environment loading

load_env(**kwargs)

Loads a .env file into os.environ. Unlike calling python-dotenv directly, this function can locate .env files that are packaged inside eggs or other archive formats via the importlib.resources API.
1

Install the optional dependency

pip install alex-ber-utils[python-dotenv]
2

Call load_env at application startup

from alexber.utils.mains import load_env

# Option 1 — path on disk
load_env(dotenv_path='/path/to/.env')

# Option 2 — stream (e.g. from a StringIO or vault secret)
import io
load_env(stream=io.StringIO('MY_VAR=hello'))

# Option 3 — locate .env inside an installed package (works in eggs)
load_env(ENV_PCK='myapp.config', ENV_NAME='.env')

# Option 4 — no arguments; falls back to python-dotenv defaults
load_env()
ParameterDescription
ENV_PCKDotted package name containing the .env file. Used with importlib.resources.
ENV_NAMEFilename of the .env file inside ENV_PCK. Defaults to ".env".
dotenv_pathAbsolute or relative path to a .env file on disk.
streamA StringIO or file-like object with .env content.
When both dotenv_path/stream and ENV_PCK are absent, all remaining kwargs are forwarded directly to python-dotenv’s load_dotenv().

fix_env(**kwargs)

Prepends the absolute path prefix of a reference package to selected os.environ entries. Useful when the environment contains relative path fragments that must be resolved against the package’s installation directory at runtime.
from alexber.utils.mains import fix_env

# Suppose os.environ['PLUGIN_PATH'] = 'plugins/core'
# After fix_env, it becomes '/installed/path/to/myapp/plugins/core'
fix_env(
    ENV_KEYS='PLUGIN_PATH',
    ENV_MAIN_PCK='myapp',
)
ParameterDescription
ENV_KEYSComma-separated names of os.environ keys to fix.
ENV_MAIN_PCKPackage whose __init__.py directory defines the prefix.
ENV_KEY_SEPSeparator between key names. Default: ",".
ENV_SEPPath separator used inside paths. Default: os.path.sep.
ENV_DELIM_SEPDelimiter used inside an os.environ value when it contains multiple paths. Default: os.pathsep.
clsClass or dotted string for the implementation. Default: OsEnvrionPathExpender.

fix_retry_env(**kwargs)

Fixes Windows-style paths (e.g. C:\\data\\file) stored in os.environ so they resolve correctly on Linux by stripping the drive letter.
from alexber.utils.mains import fix_retry_env

# os.environ['DATA_DIR'] = 'C:\\app\\data' (written on Windows)
fix_retry_env(ENV_KEYS='DATA_DIR')
# os.environ['DATA_DIR'] is now '/app/data'
Use fix_retry_env in CI pipelines or Docker images that consume environment variables originally set on a Windows developer machine.

Type-checking utilities

These helpers are used internally but are part of the public API.

is_iterable(value)

Returns True if value is iterable, explicitly excluding None, str, and bytes.
from alexber.utils.mains import is_iterable

is_iterable([1, 2, 3])   # True
is_iterable({'a': 1})    # True
is_iterable('hello')     # False
is_iterable(None)        # False
is_iterable(42)          # False

is_mapping(value)

Returns True if value is a mapping (has a callable .items attribute).
from alexber.utils.mains import is_mapping

is_mapping({'key': 'value'})  # True
is_mapping([('key', 'val')])  # False

make_hashable(obj)

Recursively converts an object to a hashable type so it can be used as a dictionary key or set element.
  • Mappings become frozenset of (key, value) pairs.
  • Iterables become tuple.
  • Objects with __hash__ are returned unchanged.
  • Everything else is wrapped in HashableWrapper.
from alexber.utils.mains import make_hashable

key = make_hashable({'env': 'prod', 'region': ['us', 'eu']})
cache = {key: result}  # works

HashableWrapper

A fallback wrapper that makes any object hashable by delegating __hash__ to hash(str(obj)).
from alexber.utils.mains import HashableWrapper

obj = SomeUnhashableClass()
wrapper = HashableWrapper(obj)
hash(wrapper)  # works
wrapper == HashableWrapper(obj)  # True if obj == obj

OsEnvrionPathExpender and OsEnvrionPathRetry

These classes implement the logic behind fix_env() and fix_retry_env() respectively. You can pass a custom subclass (or its dotted import path) via the cls parameter of those functions to override the default behaviour.
from alexber.utils.mains import fix_env
from myapp.patches import CustomPathExpender

fix_env(
    ENV_KEYS='PLUGIN_PATH',
    ENV_MAIN_PCK='myapp',
    cls=CustomPathExpender,   # or cls='myapp.patches.CustomPathExpender'
)