greetings
 
You are here:

bbsengine6.util Specification

Status: canonical. Updated 2026-09-04.

bbsengine6.util is the catch-all utility module: display formatting, date/time operations, logging, input helpers, file/directory verification, range operations, SQL resource loading, password hashing, and ANSI stripping. This document merges the canonical spec with the handbook/util.md function index.

Thread safety

Safe

Function Notes
logentry() Handler registration guarded by threading.Lock (_log_lock). Default SysLogHandler lazily initialized on first call.
diceroll() Uses random.SystemRandom() (_dice_rng); thread-safe.
hr(), heading(), oxfordcomma(), pluralize() Pure functions.
expandrange(), collapserange(), rangestr(), printr() Pure functions.
checksum() Pure function.
ltree_to_path(), chop_last_element(), strip_ansi(), path_to_ltree() Pure string functions.
serialize_datetimes(), tobool() Pure functions.
getremoteaddr(), getcurrentloginid() Read from process environment; no shared state.
verify_dir_exists_writable(), verify_file_exists_readable(), verify_file_exists_readable_writable() Stateless filesystem checks.
timedeltastr() Pure function.
encryptpassword() Pure (no I/O; bcrypt is internally thread-safe).

Not thread-safe

Function Notes
init() Calls locale.setlocale() and time.tzset(), which mutate global process state. Call once at application startup from the main thread.

Locales

init() calls locale.setlocale(locale.LC_ALL, ""), which reads the user's locale from LC_ALL / LC_CTYPE / LANG. This drives:

Set the locale before any date/time formatting that relies on locale-specific codes.

On Debian/Ubuntu, install locale packages with:

apt install locales
dpkg-reconfigure locales

Public API

Display and formatting

hr(acs=True, width=None, end="\n", color="{boxcolor}") -> bool
heading(title: str, **kwargs) -> None
pluralize(amount: int, singular: str, plural: str, quantity=True, emoji="", determiner="a", **kw) -> str
oxfordcomma(seq, conjunction="and") -> Optional[str]
filedisplay(res, **kw) -> None

Date and time

datestamp(t=None, format="%Y-%m-%d %I:%M%P %Z (%a)") -> str
timedeltastr(delta) -> str

timedeltastr (formerly timedelta_) converts a datetime.timedelta to a compact duration string with only the non-zero units, zero-padded to two digits:

>>> from datetime import timedelta
>>> timedeltastr(timedelta(days=2, hours=3, minutes=15, seconds=30))
'02d03h15m30s'

Logging

logentry(message, *, level=logging.INFO, handler=None, formatter=None, logger_name="bbsengine6") -> None

The default handler is a lazily-initialized SysLogHandler. The first call from a given thread may pay the setup cost; subsequent calls are fast. String level aliases ("debug", "info", "warning", "error") are accepted for convenience.

Input

inputpassword(prompt="password: ", mask="X", **kwargs) -> str

Range operations

collapserange(lst: list) -> list
expandrange(txt: str) -> list
rangestr(ranges) -> str
printr(ranges) -> None

expandrange("1-3,5,7-9") returns [1, 2, 3, 5, 7, 8, 9]. The collapserange / rangestr round-trip is the canonical wire format for page ranges and similar.

Filesystem

verify_dir_exists_writable(dirname, **kw) -> bool
verify_file_exists_readable(filename, **kw) -> bool
verify_file_exists_readable_writable(filename, **kw) -> bool

The legacy camelCase aliases (verifyDirExistsWritable, verifyFileExistsReadable, verifyFileExistsReadableWritable) remain as DeprecationWarning-emitting shims that call the snake_case forms.

Password hashing

encryptpassword(plaintextpassword: str) -> str

Compute a bcrypt hash locally. Thin wrapper around bbsengine6.password.hash_password. Returns a $2b$06$... string of length 60. This is the single source of truth for new password hashes; both member.setpassword and the legacy alias route through it.

getencryptedpassword(args, plaintextpassword) -> Optional[str]

Compatibility wrapper kept as a thin shim over encryptpassword. The args parameter is accepted but ignored; new code should call encryptpassword directly.

Random

diceroll(sides=6, count=1, mode="single")

Backed by random.SystemRandom(); cryptographically strong.

Locale init

init(args=None, **kw) -> None

Set locale from environment. Call once at application startup, before any locale-dependent formatting.

Hashing and string ops

checksum(data: bytes) -> str
tobool(value) -> bool
strip_ansi(s: str) -> str
serialize_datetimes(data: dict) -> dict

ltree helpers

ltree_to_path(ltree: str) -> str
path_to_ltree(path: str) -> str
chop_last_element(ltree: str) -> str
pathToLtree(path: str) -> str

pathToLtree is a legacy camelCase alias for path_to_ltree and is scheduled for removal; new code should use path_to_ltree.

Environment / runtime

getremoteaddr() -> Optional[str]
getcurrentloginid(args, **kwargs) -> str | None

Path and SQL resource loading

get_safe_path(args, *components, **kwargs) -> str
load_sql(args, resource_name, *, package: Optional[str] = None) -> str

load_sql reads a .sql resource from bbsengine6.sql (default) or the package named in package=.

Encryption (at-rest helpers)

get_encryption_key() -> bytes
encrypt_password(plaintext: str) -> str
decrypt_password(ciphertext_b64: str) -> str

These are the at-rest encryption helpers used by the message queue and similar subsystems; they are distinct from the bcrypt password hashing above.

Argv sanitization

sanitize_args(argv)
safe_main(...)

safe_main wraps main() with the canonical argv sanitization pass that strips DB credentials from --psql-* and similar arguments before they appear in process listings.

Reference: function index

The names exported from bbsengine6.util, mirroring the short index in handbook/util.md (with current names; legacy aliases noted where they appear in the public surface).

Function Purpose
hr(), heading(), pluralize(), filedisplay() Display formatting
datestamp(), timedeltastr() Date/time
inputpassword() Masked password prompt
oxfordcomma() List-to-prose join
logentry() Syslog logging
collapserange(), expandrange(), rangestr(), printr() Range operations
diceroll() Cryptographically strong dice rolls
timedeltastr() Human-readable timedelta rendering
encryptpassword() bcrypt hash (canonical)
init() Locale init
checksum() Byte-level hash
ltree_to_path(), path_to_ltree(), chop_last_element() ltree conversions
tobool() Coerce truthy strings to bool
getremoteaddr(), getcurrentloginid() Environment reads
get_safe_path(), load_sql() Path / SQL resource loading
serialize_datetimes() JSON-friendly datetime flattening
strip_ansi() ANSI escape stripping
getencryptedpassword() Legacy alias of encryptpassword
verify_dir_exists_writable(), verify_file_exists_readable(), verify_file_exists_readable_writable() Filesystem checks
get_encryption_key(), encrypt_password(), decrypt_password() At-rest encryption

Dependencies

Known issues

  1. datestamp() calls time.tzset() on every invocation, which is redundant and modifies global state. The init path should set the timezone once.
  2. logentry() string level aliases ("debug", "info", etc.) are accepted but an enum would be more type-safe.