os_helper package
Submodules
- os_helper.api module
- os_helper.config_utils module
- os_helper.gettext_utils module
- os_helper.gui module
- os_helper.hardware_utils module
- os_helper.hash_utils module
- os_helper.i18n_utils module
- os_helper.logging_utils module
- os_helper.mcp module
- os_helper.misc_utils module
- os_helper.path_utils module
- os_helper.profile_utils module
- os_helper.prompts_utils module
- os_helper.string_utils module
- os_helper.system_utils module
- os_helper.temp_utils module
Module contents
OS Helper
A collection of cross-platform utility functions covering: - file and directory handling - system / OS detection and process helpers - hardware inspection (CPU cores/model, RAM, GPU vendor/model/VRAM, Apple Silicon) - string manipulation and ASCII normalization - temporary files and folders (including remote staging) - hashing of strings, files, and folders - configuration loading (JSON / YAML / .env / environment) - URL checks, downloads, zipping - logging and verbosity helpers
Usage Example
>>> import os_helper as osh
>>> osh.verbosity(1)
>>> if osh.unix():
... osh.info("running on a Unix-based OS")
>>> osh.info(osh.now_string("filename"))
>>> h = osh.hash_string("hello", size=8)
There is no command line: this package is the suite’s library foundation and
every member reaches it by import. The same safe helpers are also exposed over
HTTP ([api]), as MCP tools ([mcp]) and through the Tree Radar dashboard
([gui]). See the README and GUI.md for the multi-surface story.
- class os_helper.Catalog(locales_dir, domain, default_language)[source]
Bases:
objectCompiled message catalogues for one package, read through gettext.
Build one with
from_package()rather than calling the constructor, so the directory layout is resolved once and in one place.- locales_dir
The
locales/root. Used in error messages, so a failure names the directory to fix.- Type:
- default_language
The language served when the caller asks for one that has no catalogue. The suite’s GUIs default to French; English needs no catalogue because it is the msgid.
- Type:
- classmethod from_package(locales_dir, domain, *, default_language='fr')[source]
Resolve the catalogue directory for a package.
- Parameters:
locales_dir (str or pathlib.Path) – Conventionally
<package>/locales, i.e. inside the importable package directory. Outside it, the catalogues do not travel in the wheel and every string raises at run time on an installed copy.domain (str) – The gettext domain; by convention the package’s import name.
default_language (str, keyword-only) – Language for an unknown or absent request.
- Returns:
A catalogue handle. Nothing is read from disk until a lookup.
- Return type:
- property languages: tuple[str, ...]
Every language this package can render, sorted.
The source language is always present — it is the msgid — followed by each
<lang>/LC_MESSAGES/<domain>.moactually on disk. A.powith no compiled.mois deliberately not listed: it cannot be served, and claiming it would offer a language that renders English.
- negotiate(header)[source]
Best language from an HTTP
Accept-Languageheader, orNone.Honours
qweights and falls back to a base language, so a browser asking forfr-CA;q=0.9, en;q=0.8gets thefrcatalogue.Parsed here rather than with a header library: this is one header, the grammar is a list of tags with optional weights, and the suite does not take a dependency for forty lines of standard parsing.
- Parameters:
header (str or None) – The raw header value.
- Returns:
The language to serve, or
Nonewhen the best match is the source language or when nothing offered has a catalogue — both of which mean “leave the source text alone”. ReturningNonerather than"en"lets a caller distinguish “the visitor asked for English” from “the visitor asked for nothing we have”.- Return type:
str or None
Notes
The source language counts as offered even though it has no catalogue, and that is not a technicality: without it, a visitor who asked for English first and French second would be served French, because the language they actually wanted was the one the loop could not see.
- ngettext(language, singular, plural, n)[source]
Translate a message whose wording depends on a count.
- Parameters:
language (str or None) – Requested language, resolved through
resolve()first.singular (str) – The two English source forms. The pair is the msgid: gettext stores it NUL-joined, which is what
build_mo()writes.plural (str) – The two English source forms. The pair is the msgid: gettext stores it NUL-joined, which is what
build_mo()writes.n (int) – The count that picks the form. Which form that is comes from the catalogue’s own plural rule, not from
n == 1— the point of asking gettext rather than branching at the call site.
- Returns:
The translated form, or the matching English source when the catalogue has no entry.
- Return type:
- pgettext(language, context, message)[source]
Translate one source string in a given context.
The answer to a word that is one word in English and two in French:
pgettext("month name", "May")andpgettext("verb", "May")are separate entries, because a translator cannot disambiguate them from the msgid alone.- Parameters:
- Returns:
The translation, or message itself when this context has no entry — never the
contextmessagekey, which would put a control character in front of a reader.- Return type:
- problems(*, used=None)[source]
Everything wrong with this package’s catalogues, as sentences.
Checks, in order:
a
.powith no compiled.mo, or a.mothat no longer matches its.po— a translation edited and not recompiled;an entry whose translation is empty, which would quietly serve English from a language that claims to be translated;
when used is given, a string the code asks for that no catalogue carries, and a catalogue entry nothing asks for.
That third check is the one that matters most under this shape. Because the msgid is the English copy, rewording a sentence in the template orphans its translation, gettext falls back to English, and the page half-reverts with nothing raised. Passing the set of strings the code actually uses turns that into a failure a test can see.
- resolve(language)[source]
The language that will actually be served for a request.
- Parameters:
language (str or None) – The requested code, typically straight off a
?lang=parameter.- Returns:
language when it has a catalogue (or is the source language), its base language when only that has one (
fr-CA→fr), otherwisedefault_language.- Return type:
- class os_helper.Locales(document, path)[source]
Bases:
objectA parsed
locales/i18n.yaml: sections of keys, each with one text per locale.Build one with
from_path()rather than calling the constructor, so the file is validated as it is read.- path
Where it was read from. Used in error messages, so a failure names the file to fix.
- Type:
- classmethod from_path(path)[source]
Parse a locale file, failing loudly if it cannot serve its purpose.
- Parameters:
path (str or pathlib.Path) – Path to the YAML file, conventionally
locales/i18n.yamlat the package root.- Returns:
The parsed file.
- Return type:
- Raises:
FileNotFoundError – If the file is absent.
RuntimeError – If it does not parse as a YAML mapping, or lacks a usable
metasection. A malformed locale file must fail at load rather than surface later as a puzzlingKeyErrordeep in a caller.
- problems(*, require_parity=True, sections=None)[source]
Return everything wrong with this file, as sentences, worst first.
Built for a test to assert on (section 21.3.7). It reports all the problems rather than raising on the first, because a translator fixing a file wants the whole list in one pass.
- Parameters:
require_parity (bool, optional) – When true (the default), a key whose translations do not all take the same
{placeholder}fields is reported. Parity matters because a template missing a field renders a sentence with a hole in it, in that language only.sections (list of str, optional) – Which sections to check. Defaults to every content section except those listed in
unlocalized_sections, since a section that is deliberately single-language has no translations to miss.
- Returns:
One sentence per problem. Empty when the file is sound.
- Return type:
Examples
>>> import os_helper as osh >>> osh.Locales.from_path("locales/i18n.yaml").problems() []
- raw(section, key)[source]
Return one entry exactly as written, with no locale resolution.
The accessor for an unlocalized section, whose sub-keys are not locales and so cannot be resolved by
text().
- render(section, key, locale=None, **values)[source]
Return a key’s text with its
{placeholder}fields filled in.Templates are data, never code (section 21.3.5): this formats, it never evaluates, and it checks the placeholder names first so a caller learns what it forgot instead of shipping a half-filled sentence.
- Parameters:
section (str) – Section name.
key (str) – Semantic key within that section.
locale (str, optional) – Wanted locale. Defaults to
default_locale.**values – One argument per placeholder in the template.
- Returns:
The filled text.
- Return type:
- Raises:
KeyError – If the template needs a placeholder that values does not carry.
- text(section, key, locale=None)[source]
Return the text for one key, falling back to the default locale.
- Parameters:
section (str) – Section name.
key (str) – Semantic key within that section.
locale (str, optional) – Wanted locale. Defaults to
default_locale.
- Returns:
The text in locale, or in the default locale when locale has no entry for this key.
- Return type:
- Raises:
KeyError – If the key is absent, or carries neither the wanted locale nor the default one. Returning the raw key instead would push a developer’s identifier in front of a user (section 21.3.4).
- property unlocalized_sections: list[str]
Sections whose keys are deliberately not indexed by locale.
A product may keep one section in a single language on purpose, and then its sub-keys mean something else entirely — a role, a variant, a field name. Declaring them here is what stops
problems()from reading those sub-keys as locale codes and reporting a wall of translations that were never meant to exist.Declared in the file, so the loader stays ignorant of what any section is for:
meta: unlocalized_sections: [templates]
- class os_helper.Prompts(locales, section)[source]
Bases:
objectOne section of a locale file, read as model-facing templates.
Build one with
from_path(); the constructor takes an already-parsedLocalesfor callers that have one.- locales
The parsed file. Reused rather than re-read, so a package that also has a
guisection pays for one parse.- Type:
- section
Which section holds the prompts. Named by the caller because os-helper has no business knowing that prompts are a thing — it is the member that decides, and members differ (
promptshere,bundlethere).- Type:
- bundle(language=None)[source]
Every prompt for one language at once.
This is how prompts are actually consumed: a caller resolves the language once, then reads several templates. Asking key by key means resolving the language again for each, which is both slower and an invitation to resolve it inconsistently.
- Parameters:
language (str or None) – Wanted language, resolved through
resolve().- Returns:
{key: text}for simple prompts,{key: {field: text}}for fielded ones. Values come back as stored, so a key holding a structure for that language (a nested sub-tree, not a template) survives intact. A key with nothing for this language and nothing for the default is omitted rather than raising: a bundle is a view, and a caller that needs one key should ask for it by name and get the error.- Return type:
- classmethod from_path(path, section='prompts')[source]
Read a locale file and take one section of it as prompts.
- Parameters:
path (str or pathlib.Path) – The
locales/i18n.yamlto read. Must live inside the importable package directory, or it is not package data and never reaches the wheel.section (str) – The section holding the templates.
- Returns:
A handle. The file is parsed and validated now, by Locales.
- Return type:
- property languages: tuple[str, ...]
Every language any prompt in this section is written in, sorted.
Taken from the prompts themselves rather than from
meta: a package whose interface is bilingual may well have prompts in three languages, or in one, and the file should not have to lie about either.
- problems()[source]
Everything wrong with this section, as sentences.
Checks, in order:
a prompt missing a language that the others have — the reader gets a different language than the one they asked for, which for a model means a different answer;
a prompt whose text is empty;
a language whose template does not carry the same placeholders as the default — the failure specific to this shape. A French prompt that drops the
{criteria}the English one has will raise at render time, in production, on whichever request first asked for French. An extra placeholder is just as bad: it reachesformat()with no value and raises there instead.
- render(key, language=None, *, field=None, **values)[source]
One prompt with its
{placeholder}fields filled in.Templates are data, never code: this formats, it never evaluates. The placeholder names are checked first, so a caller learns what it forgot instead of sending a half-filled instruction to a model — which would not fail, it would just answer the wrong question.
- Parameters:
- Returns:
The formatted prompt.
- Return type:
- Raises:
KeyError – If the template needs a placeholder no value was given for.
- resolve(language)[source]
The language that will actually be served for a request.
- Parameters:
language (str or None) – A language code, possibly regional (
fr-CA) or absent.- Returns:
language when prompts exist in it, its base language when only that has them, otherwise
default_language.- Return type:
- text(key, language=None, *, field=None)[source]
One prompt, in the best available language.
- Parameters:
key (str) – The semantic key, e.g.
axis_prompt.language (str or None) – Wanted language; resolved through
resolve()first, so a regional tag or an unknown one never reaches the file.field (str, optional) – For prompts that come in parts, which part — typically
"system"or"user". Omit for a single-text prompt.
- Returns:
The template, unformatted. Use
render()to fill it.- Return type:
- Raises:
KeyError – If the key (or the field) does not exist, or carries neither the wanted language nor the default one. Raising beats returning the key: a prompt that is silently the string
axis_promptwould reach a model and produce confident nonsense.
- os_helper.absolute2relative_path(path, base_path=None)[source]
Convert a path to a relative path expressed from
base_path.- Parameters:
- Returns:
The relative path from
base_pathtopath.- Return type:
Example
>>> absolute2relative_path("/home/user/project/file.txt", "/home/user") 'project/file.txt'
- os_helper.apple_chip_name()[source]
Return the Apple Silicon chip name (e.g.
'Apple M2 Max').- Returns:
The chip name, or None on non-macOS platforms or when the
system_profiler“Chip:” line is absent (older Intel Macs).- Return type:
str or None
- os_helper.apple_unified_memory_gb()[source]
Return the Apple Silicon unified-memory pool size in GB.
- Returns:
Memory in GB, or None off macOS or when the value could not be parsed from
system_profiler.- Return type:
float or None
- os_helper.asciistring(input_string, replacement_char='-', lower=True, allow_digits=True)[source]
Convert a given string into a “safe” ASCII string by replacing accented and non-ASCII characters.
Non-ASCII characters that cannot be converted will be replaced with a specified character.
- Parameters:
input_string (str) – The input string to be converted.
replacement_char (str, optional) – The character to replace non-ASCII or unwanted characters with. Defaults to ‘-‘.
lower (bool, optional) – Whether to convert the string to lowercase. Defaults to True.
allow_digits (bool, optional) – Whether to allow digits in the resulting string. Defaults to True.
- Returns:
A “safe” ASCII string with unwanted characters replaced and case adjusted.
- Return type:
Examples
>>> asciistring("MyFile@2024.txt") 'myfile-2024-txt'
>>> asciistring("Café-Con-Leche!", replacement_char="_") 'cafe_con_leche'
>>> asciistring("Special#File$2024", lower=False) 'Special-File-2024'
- os_helper.available_ram_gb()[source]
Return system RAM currently free (not committed to any process), in GiB.
Binary units, matching
ram_gb(); see its note on why this differs fromdisk_usage_gb().Unlike
ram_gb()(a static hardware fact: total installed memory), this is a live figure that shrinks as other processes — including an already-running local inference server — consume memory, and grows again once they release it. Consumers that only care about total capacity should keep usingram_gb(); this is for callers that need to know what is realistically usable right now.- Returns:
Free RAM in GB, per
psutil.virtual_memory().available(accounts for reclaimable OS caches/buffers, so it is more representative of “usable now” than the raw “free” figure some tools report).- Return type:
Examples
>>> 0 <= available_ram_gb() <= ram_gb() True
- os_helper.build_mo(entries)[source]
Serialise
{msgid: msgstr}into the binary gettext reads.Layout, in order: a 28-byte header, the originals’
(length, offset)table, the translations’ table, then the two blobs. Entries must be sorted by msgid — gettext binary-searches them. A plural entry arrives here already NUL-joined on both sides, which is how the format carries it.- Parameters:
entries (dict of str to str) – The catalogue, typically from
parse_po().- Returns:
A complete
.mofile.- Return type:
- os_helper.check(condition, msg='Assertion failed')[source]
Assert a condition, logging an error and raising if it fails.
- Parameters:
- Raises:
AssertionError – If
conditionis falsy.- Return type:
None
- os_helper.checkfile(filepath, msg='', check_empty=False)[source]
Assert that a file exists and, optionally, that it is non-empty.
- Parameters:
- Raises:
AssertionError – If the file does not exist (or is empty when
check_emptyis True).- Return type:
None
Example
>>> checkfile("data.csv", msg="Data file missing", check_empty=True)
- os_helper.compile_catalogs(locales_dir, *, check=False)[source]
Compile every
.pounder locales_dir to the.mobeside it.- Parameters:
locales_dir (str or pathlib.Path) – The
locales/root holding<lang>/LC_MESSAGES/<domain>.po.check (bool, keyword-only) – Recompile in memory and compare instead of writing. This is what CI wants: a translation edited without recompiling cannot then reach a release looking live.
- Returns:
With
check=False, the.mofiles written. Withcheck=True, the.mofiles that are stale or missing — empty means in sync.- Return type:
list of pathlib.Path
- os_helper.copyfile(source, destination)[source]
Copy a file from
sourcetodestination, preserving metadata.If
destinationis an existing directory, the source file name is appended to it (mirroringcpsemantics).- Parameters:
- Raises:
AssertionError – If the source file does not exist or is empty, or if source and destination resolve to the same path.
OSError – If the underlying
shutil.copy2call fails (propagated as-is).
- Return type:
None
Example
>>> copyfile("source.txt", "backup/source_backup.txt")
- os_helper.cpu_count_logical()[source]
Return the number of logical CPUs (including hyperthreads/SMT).
- Returns:
os.cpu_count(), or 1 if the platform refuses to report it.- Return type:
Examples
>>> cpu_count_logical() >= 1 True
- os_helper.cpu_count_physical()[source]
Return the number of physical CPU cores (excluding hyperthreads/SMT).
Delegates to
psutil, which already normalizes this across platforms (it reads/proc/cpuinfocore ids on Linux,sysctlon macOS, and the WMI processor table on Windows).- Returns:
Physical core count, or None when psutil cannot determine it (rare, e.g. some container sandboxes).
- Return type:
int or None
- os_helper.cpu_model()[source]
Return a human-readable CPU model string for the current machine.
- Parameters:
None
- Returns:
e.g.
'Apple M2 Max','AMD Ryzen 9 7950X','Intel(R) Xeon(R) Platinum 8358 CPU @ 2.60GHz'. None if no probe on this platform yielded a usable string.- Return type:
str or None
Examples
>>> cpu_model() is None or isinstance(cpu_model(), str) True
- os_helper.cpu_percent()[source]
Return instantaneous CPU utilization as a percentage (0-100).
Delegates to
psutil.cpu_percent, sampled over a short blocking window so the figure reflects genuinely current load rather than the misleading0.0psutil returns on a first call with no interval. A live figure, not a hardware fact — call this only where a short (0.1s) blocking sample is acceptable (diagnostics, one-shot CLI reports); do not call it in a hot loop.- Returns:
CPU utilization percent, 0-100.
- Return type:
Examples
>>> 0.0 <= cpu_percent() <= 100.0 True
- os_helper.cpu_timer()[source]
Measure CPU time consumed by the current process using
time.process_time()(sums user + system CPU across all threads).Differs from
wall_timer()in two important ways:It excludes time spent blocked on I/O, sleeping, or waiting on the GPU — so it isolates “actual computation done by Python+native code”.
It excludes subprocesses (ffmpeg, etc.). For those, use
wall_timer()oros.times()directly.
On a multi-threaded computation it can report more seconds than wall-clock — that’s intentional: it counts the CPU work, not the elapsed time.
- Yields:
dict –
{"seconds": float, "milliseconds": float}.- Return type:
Examples
>>> with cpu_timer() as t: ... total = sum(i * i for i in range(1_000_000)) >>> assert t["seconds"] > 0
- os_helper.critical(msg, *args, **kwargs)[source]
Log a message at CRITICAL level via the dedicated
os_helperlogger.- Parameters:
msg (str) – The (possibly %-style) message template.
*args (Any) – Positional interpolation arguments forwarded to
logging.critical.**kwargs (Any) – Keyword options forwarded to
logging.critical.
- Return type:
None
- os_helper.debug(msg, *args, **kwargs)[source]
Log a message at DEBUG level via the dedicated
os_helperlogger.- Parameters:
msg (str) – The (possibly %-style) message template.
*args (Any) – Positional interpolation arguments forwarded to
logging.debug.**kwargs (Any) – Keyword options (e.g.
exc_info) forwarded tologging.debug.
- Return type:
None
- os_helper.dir_exists(path, check_empty=False)[source]
Check if a directory exists, with an option to verify it’s not empty.
- Parameters:
- Returns:
True if the directory exists (and is not empty if check_empty is True), False otherwise.
- Return type:
Example
>>> dir_exists("/path/to/folder") True >>> dir_exists("/path/to/empty_folder", check_empty=True) False
- os_helper.disk_usage_gb(path=None)[source]
Return free space and percent used for the filesystem holding
path.Decimal units (1e9), not binary: drives are sold and labelled decimal, so a 500 GB disk reads as ~500 here. This deliberately differs from
ram_gb(), which is binary – the two figures are each in their own conventional unit and must not be compared directly or summed.- Parameters:
path (str or None) – Any path on the filesystem to report on. Defaults to the user’s home directory, since that is where most local caches (model weights, package caches, build artifacts) actually live and eventually fill up.
- Returns:
{"free_gb": float, "used_gb": float, "total_gb": float, "percent_used": float}.- Return type:
Examples
>>> usage = disk_usage_gb() >>> 0.0 <= usage["percent_used"] <= 100.0 True
- os_helper.download_file(url, file_path='', *, chunk_size=None, progress=True, check_url=True, resume=True, retries=3, sha256=None, overwrite=False)[source]
Download a URL to a local file: streamed, resumable, retried, atomic, verified.
This is the suite-wide download primitive; every helper that fetches bytes from HTTP (model mirrors, media, templates, catalogs) routes through it so the smart behaviour lives in one place:
Streamed block-by-block, so the payload is never held in memory (a multi-GB model downloads with a flat footprint). The block size adapts to the payload via
_adaptive_chunk_size()(~500 progress updates, clamped[64 KiB, 4 MiB]); passchunk_sizeto override.Resumable (
resume=True): bytes land in a<file>.partsidecar, and an interrupted transfer continues with an HTTPRangerequest instead of restarting. If the server ignoresRange(answers200instead of206), the sidecar is restarted from zero automatically.Retried (
retriesattempts) on transient network errors, with exponential backoff, each retry resuming from the current sidecar size.Atomic: the finished sidecar is
os.replace-d ontofile_pathin one step, sofile_pathonly ever exists as a complete download; a crash leaves a.part, never a truncated final file.Verified (optional
sha256): the finished file’s digest is checked and a mismatch raises (the bad sidecar is discarded).Idempotent (
overwrite=False): an existing, completefile_pathis reused without re-downloading (its digest re-checked whensha256is given), so callers can invoke it unconditionally.
A
tqdmprogress bar is shown on an interactive terminal (auto-suppressed off-TTY). Progress and completion are logged viainfo(), retries viawarning(), hard failures viaerror(). Iffile_pathis empty, the name is derived from the URL’s last segment.- Parameters:
url (str) – The URL to download from.
file_path (str, optional) – Destination path. Defaults to the URL’s last path segment.
chunk_size (int or None, optional) – Streaming block size in bytes.
None(default) adapts to the size.progress (bool, optional) – Show a progress bar on an interactive terminal (default
True).check_url (bool, optional) – Pre-validate the URL with a HEAD request (default
True). SetFalsewhen the server rejects HEAD (405/403).resume (bool, optional) – Continue a partial
<file>.partvia HTTPRange(defaultTrue).Falsediscards any sidecar and downloads from scratch.retries (int, optional) – Extra attempts on transient network errors, with exponential backoff (default
3, so up to four tries total).sha256 (str or None, optional) – Expected lowercase hex SHA-256. When given, the finished file is verified and a mismatch raises
ValueError.overwrite (bool, optional) – Re-download even if
file_pathalready exists (defaultFalse: reuse a complete destination, re-checkingsha256when supplied).
- Returns:
{"path": <destination>, "content_type": <server MIME or "">, "bytes": <size on disk>, "sha256": <hex or "">, "resumed": <bool>}.- Return type:
dict of str to object
- Raises:
AssertionError – If the URL fails the
is_working_url()precondition.ValueError – If
sha256is given and the finished file’s digest does not match.requests.RequestException – If every attempt fails or the server returns a non-2xx status.
- os_helper.emptystring(s)[source]
Return True if
sis None, not a string, or only whitespace.Convenient for input validation where
"",Noneand" "should all be treated the same way.- Parameters:
s (Optional[str]) – The value to check.
- Returns:
True when
sis None or contains only whitespace; False otherwise.- Return type:
Examples
>>> emptystring("") True >>> emptystring(" ") True >>> emptystring(None) True >>> emptystring("hello") False
- os_helper.error(msg, *args, **kwargs)[source]
Log a message at ERROR level via the dedicated
os_helperlogger.Note: this is a non-raising logger call. It will not terminate the program. Use
check()(assertion-style) or raise an exception explicitly if you need failure semantics.- Parameters:
msg (str) – The (possibly %-style) message template.
*args (Any) – Positional interpolation arguments forwarded to
logging.error.**kwargs (Any) – Keyword options forwarded to
logging.error.
- Return type:
None
- os_helper.extract_call_sites(*paths)[source]
Every msgid a Python source asks for, read from the syntax tree.
The companion to
extract_sources(), which scans template markup. Some members keep their human-facing strings in_()calls spread across modules rather than in one template, and for those the template scanner finds nothing.Why this matters beyond convenience:
Catalog.problems()can only report a catalogue entry nothing uses — the orphan left behind when a source string is reworded — if it is handed the complete set of strings the code asks for. An incomplete set turns that half of the check into noise. On md2star, a first attempt that collected only bare_("…")literals reported sixty-nine dead entries of which none was dead, and the check had to be left unasserted.The three shapes are collected in the form the catalogue stores them, so the two sides compare directly:
_("text")andgettext("text")yield"text".ngettext("one", "many", n)yields"one\many"— the plural separator gettext itself uses, so a plural entry is not reported as an orphan just because the singular was collected alone.pgettext("context", "text")yields"contexttext", likewise.
Only literal arguments are collected, because only those are knowable without running the code. A call like
_(message)wheremessageis a variable is invisible here, which is why this function makes the code-uses-it-but-the-catalogue-does-not direction sound and the orphan direction only as sound as the caller’s own coverage. Say which one you are asserting.- Parameters:
*paths (str or Path) – Files or directories. A directory is walked for
*.py, skipping.venv,buildand__pycache__— a vendored dependency’s strings are not this package’s.- Returns:
The msgids, keyed as the catalogue keys them.
- Return type:
Examples
>>> import pathlib, tempfile >>> folder = pathlib.Path(tempfile.mkdtemp()) >>> code = [ ... 'from .i18n import ngettext as _n', ... '_("Save")', ... '_n("%d file", "%d files", n)', ... ] >>> _ = (folder / 'm.py').write_text(chr(10).join(code)) >>> found = extract_call_sites(folder) >>> sorted(m.replace(chr(0), ' | ') for m in found) ['%d file | %d files', 'Save']
- os_helper.extract_sources(*texts)[source]
Every English source string marked for translation in texts.
The extraction step a gettext workflow normally gets from
pybabel, scoped to the one marker this suite uses. It exists so that a member’s test can handCatalog.problems()the strings the code actually asks for, rather than a hand-maintained list that drifts from the markup — which is the only way the reworded-source fault gets caught.Before this, eight members each kept their own copy of the same four-line regex, which is exactly the duplication a shared loader is for.
- Parameters:
*texts (str) – Anything carrying markers — typically a module’s HTML template. Pass several and the results are unioned.
- Returns:
The msgids found. Empty when nothing is marked, which a caller should treat as suspicious rather than as “nothing to translate”.
- Return type:
Examples
>>> sorted(extract_sources('<h1>{{_("Run")}}</h1><p>{{_("Done.")}}</p>')) ['Done.', 'Run']
- os_helper.file_exists(file_path, check_empty=False)[source]
Check if a file exists, with an option to verify it’s not empty.
- Parameters:
- Returns:
True if the file exists (and is not empty if check_empty is True), False otherwise.
- Return type:
Example
>>> file_exists("example.txt") True >>> file_exists("empty.txt", check_empty=True) False
- os_helper.folder_description(path, recursive=True, index_html=True, with_size=True, description_json=True)[source]
List a folder’s files (with sizes) and optionally emit an HTML / JSON index.
Walks
path(recursively by default), skips hidden entries (names starting with"."), and returns a mapping from each file’s path relative topathto its size in bytes. Whenindex_htmlis True, a Bootstrap-styledindex.htmlis written intopath; whendescription_jsonis True, adescription.jsoncompanion file is written as well.- Parameters:
path (str) – Path to the folder to describe (must exist).
recursive (bool, optional) – If True, descend into subdirectories. Defaults to True.
index_html (bool, optional) – If True, write
path/index.htmllinking each entry. Defaults to True.with_size (bool, optional) – If True, include a human-readable size column in the HTML index. Defaults to True.
description_json (bool, optional) – If True, write
path/description.jsonwith the raw mapping. Defaults to True.
- Returns:
Mapping of
relative_file_path -> size_in_bytesfor every non-hidden file found.- Return type:
- Raises:
AssertionError – If
pathdoes not exist or is not a directory.
- os_helper.folder_name_ext(path, checkpath=False)[source]
Decompose a file or folder path into (folder, basename, extension).
The split happens at the last dot in the basename, so multi-part suffixes like
.tar.gzare not collapsed into one extension. Use"basename.extension"to recover the original file name.Returns an empty extension for: - directories, - files whose basename contains no dot.
- Parameters:
- Returns:
(folder, basename, extension)whereextensionexcludes the leading dot.- Return type:
Examples
>>> folder_name_ext("/path/to/file.txt") ('/path/to', 'file', 'txt') >>> folder_name_ext("/path/to/archive.tar.gz") ('/path/to', 'archive.tar', 'gz') >>> folder_name_ext("/path/to/folder") # existing directory ('/path/to', 'folder', '')
- os_helper.format_size(size)[source]
Convert a byte count into a short human-readable string.
Picks an appropriate SI-style unit (B, KB, MB, GB, TB) using decimal thresholds (1000-based). Useful for log lines and progress reporting.
- os_helper.get_config(keys, config_type, path=None, env_files=None, *, allow_ambient_env=True)[source]
Load configuration settings using a fixed fallback order.
Precedence (first match wins):
pathpointing to a JSON/YAML file (or, if a directory, the first.json,.yamlor.ymlfile in it that contains all keys);one or more
.envfiles merged intoos.environ;the current process environment (skipped when
allow_ambient_envisFalse).
- Parameters:
keys (List[str]) – Keys required to be present in the resolved configuration.
config_type (str) – Human-readable label used only in log messages.
path (Optional[str], optional) – Path to a configuration file or a directory containing one. If None or empty, this step is skipped.
env_files (Optional[List[str]], optional) –
.envfiles to load intoos.environbefore reading variables. Defaults to[".env"].allow_ambient_env (bool, optional) – When
True(the default, and the only behavior before this parameter existed), step 3 reads the live process environment — which includes both the requested.envfiles and whatever the process happened to inherit at start-up. WhenFalse, step 3 is restricted to exactly the requestedenv_files’ own contents (read directly, never merged into or read back fromos.environ): a key that resolves only because it happens to be set in the ambient environment is treated as unresolved. Used by the HTTP API (seeos_helper.api), where “the caller can name any environment-variable key and get its live value back” is a credential-exposure shape, not a feature — a local CLI/library caller already has that access by definition, so this only ever needs to beFalseacross a network boundary.
- Returns:
Mapping with one entry per requested key.
- Return type:
- Raises:
RuntimeError – If none of the sources provide all required keys.
Example
>>> config = get_config(["host", "port"], "database", path="config.yaml") >>> config {'host': 'localhost', 'port': 5432}
- os_helper.get_nb_workers(workers=-1)[source]
Resolve a worker count, following scikit-learn’s
n_jobsconvention.The default pool size is
os.cpu_count()(or 1 if that returns None), overridable via theNB_WORKERSenvironment variable.- Parameters:
workers (int, optional) –
0: use the full pool size.> 0: use exactly that many workers.< 0: usepool_size + workers + 1(e.g.-1→ all CPUs,-2→ all but one), clamped to at least 1.
- Returns:
The resolved worker count (always >= 1).
- Return type:
Example
>>> get_nb_workers() # -1 → all available CPU cores 4
- os_helper.get_user_ip()[source]
Fetch the caller’s public IPv4 and IPv6 addresses via the ipify API.
- Returns:
{"ipv4": <str or None>, "ipv6": <str or None>}. An individual entry is None when its endpoint fails or returns no address.- Return type:
- Raises:
AssertionError – If both endpoints fail and no address could be retrieved.
- os_helper.getpid()[source]
Return the current process ID as a string.
- Returns:
str(os.getpid()).- Return type:
- os_helper.gpu_timer(backend='auto')[source]
Measure GPU execution time, synchronizing before and after the block.
Backends
"cuda"— usestorch.cuda.Event(enable_timing=True)pairs, which give microsecond-level GPU-side timing."mps"— Apple Silicon. PyTorch’s MPS backend does not expose timing events, so this falls back totorch.mps.synchronize()+time.perf_counter()around the block. Accuracy ~1 ms."auto"— pick CUDA if available, else MPS, else raise.
Both paths synchronize before and after the block so the measured duration corresponds to actual GPU work, not just kernel-queue submission. Without synchronization, GPU ops are asynchronous and the timer would understate the cost dramatically.
- param backend:
"auto"(default),"cuda", or"mps".- type backend:
str, optional
- Yields:
dict –
{"seconds": float, "milliseconds": float}.- raises RuntimeError:
If PyTorch is not installed, or if the requested backend is unavailable on this machine.
- raises ValueError:
If
backendis not one of"auto","cuda","mps".
Examples
>>> import torch >>> if torch.cuda.is_available(): ... x = torch.randn(2048, 2048, device="cuda") ... with gpu_timer() as t: ... y = x @ x ... print(t["milliseconds"])
- os_helper.gpu_utilization_percent(vendor=None)[source]
Return the current (live) GPU compute utilization, 0-100.
Apple Silicon (via IOKit, see
_apple_gpu_utilization_percent()— nopowermetrics/sudo needed), NVIDIA (nvidia-smi), and AMD (rocm-smi). A multi-GPU box reports the first card’s utilization only, matchinggpus()’s single-card assumption elsewhere in this module.- Parameters:
vendor (str or None) – One of
gpu_vendor()’s return values. Defaults to callinggpu_vendor()when omitted.- Returns:
Utilization percent, or None when unavailable (wrong vendor, tool not on PATH, or unparseable output) — never a fabricated number.
- Return type:
float or None
Examples
>>> util = gpu_utilization_percent("cpu") >>> util is None True
- os_helper.gpu_vendor()[source]
Identify the primary compute accelerator vendor on this machine.
Checks, in order: Apple Silicon, NVIDIA (
nvidia-smi), AMD (rocm-smi), Intel integrated graphics (Linuxlspcionly), then falls back to'cpu'.- Returns:
One of:
'apple','nvidia','amd','intel','cpu'.- Return type:
Examples
>>> gpu_vendor() in ('apple', 'nvidia', 'amd', 'intel', 'cpu') True
- os_helper.gpus()[source]
List every discrete GPU on this machine, dispatched by detected vendor.
Apple Silicon is intentionally excluded: it has no discrete VRAM pool to enumerate (see
apple_unified_memory_gb()instead).
- os_helper.hardware_info()[source]
Return a single snapshot of every hardware fact this module can detect.
Convenience aggregate over every other function in this module, useful for a one-call “what is this machine” report (CLI
detectcommands, diagnostics, bug reports). Mixes static facts (core counts, RAM capacity) with live figures (cpu_percent,available_ram_gb,disk,gpu_utilization_percent) sampled at call time — fine for a one-shot report, not for polling in a hot loop (see the live functions’ own docstrings).- Returns:
{"platform": str, "cpu": {"physical_cores": int | None, "logical_cores": int, "model": str | None, "percent": float}, "ram_gb": float, "available_ram_gb": float, "disk": {"free_gb": float, "used_gb": float, "total_gb": float, "percent_used": float}, "gpu_vendor": str, "gpus": list[dict], "gpu_utilization_percent": float | None, "apple_chip": str | None, "apple_unified_gb": float | None}.- Return type:
Examples
>>> info = hardware_info() >>> set(info) == { ... "platform", "cpu", "ram_gb", "available_ram_gb", "disk", ... "gpu_vendor", "gpus", "gpu_utilization_percent", ... "apple_chip", "apple_unified_gb", ... } True
- os_helper.hash_string(s, size=-1)[source]
Generate a hash of a given string and optionally returns a truncated version.
- Parameters:
- Returns:
The hashed string, optionally truncated.
- Return type:
Example
>>> isinstance(hash_string("example"), str) True >>> len(hash_string("example")) 40 >>> len(hash_string("example", size=8)) 8
Note
The exact digest depends on the underlying hash engine (RIPEMD-160 when available, BLAKE2b truncated to 20 bytes otherwise). The output length stays 40 hex characters either way.
- os_helper.hashfile(path, hash_content=True, hash_path=None, date=False)[source]
Generate a hash for a file’s content and/or its last modification date.
- Parameters:
path (str) – The path to the file to hash.
hash_content (bool, optional) – If True, includes the file’s content in the hash (default: True).
hash_path (bool or None, optional) –
If True, includes the path string in the hash — the way to fingerprint a name rather than bytes, for a file that need not exist. Combined with
hash_content=Trueit answers “this file, at this path”, which neither flag can express alone: two copies of the same bytes under different names share a content digest, and two different files under the same name share a path digest.Left unset (
None, the default) it mirrors the older two-spelling behaviour — the path is hashed exactly when the content is not — so every digest produced before this parameter existed is unchanged. Pass it explicitly to control the two halves independently.date (bool, optional) – If True, includes the file’s own last modification time (
os.stat().st_mtime_ns) in the hash (default: False). It is the file’s mtime, never the current clock: a digest that moved on every call could not be a cache key.
- Returns:
The resulting hash of the file as a 40-character hex string.
- Return type:
- Raises:
FileNotFoundError – If
hash_contentordateis True andpathnames nothing.IsADirectoryError – If
hash_contentordateis True andpathnames a directory.ValueError – If
hash_content,hash_pathanddateare all False, which asks for a fingerprint of nothing.
Notes
Asking for content (or an mtime) that cannot be read is an error, not a fallback. This function used to answer a path digest in that case, so
hashfile("/gone")returned a plausible 40-hex-char value for a file nobody had read — which madehashfileuseless for noticing that a file is missing, the very reason one fingerprints a file. Callers who want a path digest ask for it withhash_path=True.The three flags are the same three
hashfolderhas always taken, in the same order, so the two siblings now answer the same questions about a file and about a folder.
- os_helper.hashfolder(path, hash_content=True, hash_path=False, date=False)[source]
Generate a hash for the contents of a folder and/or its path.
- Parameters:
path (str) – The path to the folder to hash.
hash_content (bool, optional) – If True, includes the folder’s contents in the hash (default: True).
hash_path (bool, optional) – If True, includes the folder’s path in the hash (default: False).
date (bool, optional) – If True, includes the folder’s own last modification time (
os.stat().st_mtime_ns) in the hash (default: False). It is the folder’s mtime, never the current clock. Note that a directory’s mtime tracks entries appearing and disappearing, not edits made inside a file it holds — that is whathash_content=Trueis for.
- Returns:
The resulting hash of the folder and/or its contents as a 40-character hex string.
- Return type:
- Raises:
FileNotFoundError – If
hash_contentordateis True andpathnames nothing.NotADirectoryError – If
hash_contentordateis True andpathnames a file.ValueError – If
hash_content,hash_pathanddateare all False, which asks for a fingerprint of nothing.
Notes
Asking to walk a folder that is not there is an error, not a fallback. This function used to return the digest of the empty string in that case — the same constant for every missing folder, which is also what an empty existing folder hashes to and what a file handed in by mistake hashed to. Four different situations collapsed onto one plausible 40-hex-char answer, so “have these two folders diverged” replied “no” when neither existed.
- os_helper.info(msg, *args, **kwargs)[source]
Log a message at INFO level via the dedicated
os_helperlogger.- Parameters:
msg (str) – The (possibly %-style) message template.
*args (Any) – Positional interpolation arguments forwarded to
logging.info.**kwargs (Any) – Keyword options forwarded to
logging.info.
- Return type:
None
- os_helper.init_logging(*, level=20, stdout=True, log_format='%(asctime)s | %(levelname)s | %(name)s | %(message)s', date_format='%Y-%m-%d %H:%M:%S', filename=None, capture_warnings=True, reset=True, use_colors=True, propagate=False, name=None, live_stream=False)[source]
Initialize application-wide logging.
This function configures the root logger with a console handler and, optionally, a file handler. It is designed for applications, scripts, notebooks, and machine learning experiments where deterministic logging setup is useful.
- Parameters:
level (int, optional) – Logging level applied to the root logger and its handlers. Typical values are
logging.DEBUG,logging.INFO,logging.WARNING,logging.ERROR, andlogging.CRITICAL.stdout (bool, optional) – If True, console logs are sent to
sys.stdout. Otherwise, they are sent tosys.stderr.log_format (str, optional) – Format string used for log records.
date_format (str, optional) – Format string used for timestamps in log records.
filename (str | pathlib.Path | None, optional) – Optional path to a log file. If provided, logs are also written to this file using UTF-8 encoding.
capture_warnings (bool, optional) – If True, warnings emitted through the
warningsmodule are redirected to the logging system.reset (bool, optional) – If True, existing handlers attached to the root logger are removed before adding new ones. This is often desirable in notebooks and interactive sessions to avoid duplicated messages.
use_colors (bool, optional) – If True, colorize console log levels when ANSI colors are supported. File logs are never colorized.
propagate (bool, optional) – Value assigned to the target logger’s propagation flag.
Falseavoids duplicates for the root logger; a named logger (seename) often wantsTrueso its records still reach a host’s / pytest’s root handlers (e.g.caplog).name (str | None, optional) – Configure this named logger instead of the root. When set, the reset step only removes handlers this function installed (so a host’s / pytest’s handlers on that logger survive), and repeated calls are idempotent — a second call does not stack a duplicate console handler. This is the CLI-friendly mode: configure
"mytool"once, keeppropagate=True, and everylogging.getLogger("mytool.*")inherits the handler + level.live_stream (bool, optional) – If True, the console handler re-resolves
sys.stdout/sys.stderron every emit instead of binding the stream once. This keeps output flowing to wherever the stream currently points — surviving pytest’scapsys(which swaps the streams per test) and any post-config redirection. The default keeps the classic bound-stream handler.
- Returns:
The configured logger (the root logger, or the one named by
name).- Return type:
Notes
This function is intended for top-level applications, notebooks, and experimentation code. Reusable libraries should generally avoid configuring global logging and should instead use:
logger = logging.getLogger(__name__)Examples
>>> logger = init_logging(level=logging.DEBUG, filename="experiment.log") >>> logger.info("Logging is configured.")
>>> logger = init_logging(use_colors=True, reset=True) >>> logger.warning("This is a warning.")
- os_helper.is_working_url(url)[source]
Return True if
urlis syntactically valid and answers 200 to a HEAD.
- os_helper.join(*args)[source]
Join multiple path components into a single absolute, normalized path.
This is the canonical path-construction helper exposed by
os_helperand replaces the olderos_path_constructorfunction (removed in v1.1.0). It accepts either positional components or a single iterable.- Parameters:
*args (str) – The path components to join, or a single iterable of path components.
- Returns:
The absolute, normalized path.
- Return type:
Example
>>> join("folder1", "subfolder2", "file.txt") '/home/user/project/folder1/subfolder2/file.txt' >>> join(["folder1", "subfolder2", "file.txt"]) '/home/user/project/folder1/subfolder2/file.txt'
- os_helper.language_candidates(tag)[source]
A language tag plus its base, so
fr-CAcan find thefrcatalogue.Normalised the way gettext names its directories (
fr_CA, notfr-CA) and stripped of any.UTF-8/@eurosuffix a POSIX locale string carries. One normalisation here means an HTTP header and an environment variable can both be passed in.
- os_helper.linux()[source]
Determine if the current operating system is Linux.
- Returns:
True if the operating system is Linux, False otherwise.
- Return type:
- os_helper.macos()[source]
Determine if the current operating system is macOS.
- Returns:
True if the operating system is macOS, False otherwise.
- Return type:
- os_helper.make_directory(folder_path, exist_ok=True)[source]
Create a directory (and missing parents), optionally tolerating prior existence.
- Parameters:
- Raises:
OSError – If the directory cannot be created (propagated from
os.makedirs).AssertionError – If
os.makedirsreturned without error but the directory is still not visible on disk afterwards.
- Return type:
None
Example
>>> make_directory("/path/to/new_folder")
- os_helper.make_temporary_directory(prefix='', directory=None)[source]
Create a temporary directory and return its path — caller owns cleanup.
The non-context-manager companion to
temporary_folder(). Use this when the directory must outlive awithblock: a request handler that schedules deletion after streaming a response, a process-lifetime scratch dir cleaned at exit, or any case where the created path is stored and removed later. It is the suite’stempfile.mkdtemp()— the caller is responsible for removing the tree (e.g. viaremove_directory()/shutil.rmtree/atexit).- Parameters:
- Returns:
Absolute path to the freshly created directory.
- Return type:
Example
>>> work = make_temporary_directory(prefix="myjob-") >>> # ... use `work`, then clean up when you are done ... >>> remove_directory(work)
- os_helper.openfile(filename)[source]
Open a file in the platform’s default application.
Uses
os.startfileon Windows,openon macOS, andxdg-openon Linux. Exceptions from the underlying call are propagated as-is so the caller can react to them.
- os_helper.parse_po(path)[source]
Read a
.pointo the{msgid: msgstr}mapping a.mostores.Plural entries are kept: gettext stores them by NUL-joining the two
msgidforms into one key and themsgstr[n]forms into one value, which is exactly whatbuild_mo()then writes. Contexts (msgctxt) are kept the same way, joined to their message by— the format has no separate field for them, which is whyCatalog.pgettext()needs no change to the layout.An entry with an empty translation is dropped, which is what makes an unfinished catalogue fall back to its English source rather than serve emptiness. Use
untranslated()to see those instead of losing them.- Parameters:
path (str or pathlib.Path) – The
.pofile to read.- Returns:
Every translated entry, ready for
build_mo().- Return type:
dict of str to str
- os_helper.path_without_home(path)[source]
Convert an absolute path to be relative to the user’s home directory by replacing the home path with ‘~’.
- Parameters:
path (str) – The absolute path to convert.
- Returns:
The path with the home directory replaced by ‘~’, if applicable.
- Return type:
Example
>>> path_without_home("/home/user/project/file.txt") '~/project/file.txt'
- os_helper.platform_name()[source]
Return the current OS as a short lowercase string.
- Returns:
One of:
'darwin','linux','windows'.- Return type:
Examples
>>> platform_name() in ('darwin', 'linux', 'windows') True
- os_helper.progress_bar(total=None, *, desc='', disable=None, unit='B')[source]
Create a
tqdmprogress bar configured for file transfers.The suite-wide byte-transfer bar, so every helper that moves bytes (HTTP download, S3 up / download, SFTP put / get) shows the same progress UI: byte-scaled units (KiB / MiB / GiB), a known total when available (for a percentage + ETA), and — crucially — auto-suppression when ``stderr`` is not a TTY, so CI logs and piped output are never flooded with control characters. Each caller simply wraps its transfer library’s progress hook (
requestschunks, boto3Callback, paramikocallback) around one of these bars.- Parameters:
total (int or None, optional) – Total number of bytes when known (enables percentage + ETA);
Noneleaves the bar open-ended.desc (str, optional) – Short label shown at the left of the bar (e.g. the file / key name).
disable (bool or None, optional) – Force the bar on / off.
None(default) auto-disables whenstderris not an interactive terminal.unit (str, optional) – Progress unit (default
"B";unit_scalerenders raw byte counts as KiB / MiB / GiB).
- Returns:
A ready progress bar; call
.update(n)per transferred chunk and.close()when done.- Return type:
tqdm.tqdm
Examples
>>> bar = progress_bar(total=10, desc="demo", disable=True) >>> bar.total 10 >>> bar.disable True >>> bar.close()
- os_helper.ram_gb()[source]
Return total system RAM in GiB (binary, 1024**3 bytes).
The unit is binary because that is how RAM is sold and reported: a “16 GB” module is 16 GiB. Note that
disk_usage_gb()is decimal (1e9) for the same reason in reverse – drives are sold decimal – so the two are not directly comparable, despite both saying “gb”.- Returns:
Total RAM in GB. Always a positive float —
psutilis a mandatory runtime dependency, so this never raisesImportError.- Return type:
Examples
>>> ram_gb() > 0 True
- os_helper.recursive_glob(root_dir, pattern)[source]
Recursively search for files matching a glob pattern under
root_dir.Each subdirectory is walked and the pattern applied in turn, so patterns like
"*.txt"match in nested folders as well as at the top level.- Parameters:
- Returns:
File paths matching the pattern, in walk order.
- Return type:
List[str]
Example
>>> recursive_glob("/home/user", "*.txt") ['/home/user/file1.txt', '/home/user/docs/file2.txt']
- os_helper.relative2absolute_path(path, checkpath=False)[source]
Convert a relative path to an absolute path.
- Parameters:
- Returns:
The absolute path.
- Return type:
- Raises:
FileNotFoundError – If checkpath is True and the path does not exist.
Example
>>> relative2absolute_path("docs/readme.md") '/home/user/project/docs/readme.md'
- os_helper.remove_directory(folder_path)[source]
Remove a directory and all its contents.
A missing directory is treated as a no-op (logged at INFO); any other failure from
shutil.rmtreeis propagated to the caller.- Parameters:
folder_path (str) – The path to the directory to remove.
- Raises:
OSError – If the directory exists but cannot be removed (propagated from
shutil.rmtree).- Return type:
None
Example
>>> remove_directory("/path/to/temp_folder")
- os_helper.remove_files(files_list)[source]
Remove a list of files on a best-effort basis.
Missing entries are skipped and individual removal failures are logged at ERROR level without aborting the rest of the batch. This function does not raise — if you need hard-fail-on-first semantics, call
pathlib.Path(p).unlink()yourself.- Parameters:
files_list (List[str]) – A list of file paths to remove.
- Return type:
None
Example
>>> remove_files(["temp1.txt", "temp2.log"])
- os_helper.size_file(filepath)[source]
Get the size of a file in bytes.
- Parameters:
filepath (str) – The path to the file.
- Returns:
The size of the file in bytes, or -1 if the file does not exist.
- Return type:
Example
>>> size_file("example.txt") 1024
- os_helper.str2time(input_string)[source]
Parse a time string into seconds.
- Parameters:
input_string (str) – A string representing time.
- Returns:
Number of seconds parsed from the string.
- Return type:
Examples
>>> str2time("1:30:00") 5400.0 >>> str2time("1 hr 30 min") 5400.0 >>> str2time("120 s") 120.0 >>> str2time("1.5 hours") 5400.0 >>> str2time("1.5 days") 129600.0
- os_helper.system(cmd, expected_output='', check_exitcode=True, check_empty=False)[source]
Run a shell-style command via
subprocessand capture its output.The command string is parsed with
shlex.split()and executed without spawning an actual shell (shell=False), which avoids shell-injection pitfalls while still accepting a familiar command string.- Parameters:
cmd (str) – Command line to execute (e.g.,
"ffmpeg -i in.mp4 out.mp3").expected_output (str, optional) – If non-empty, a file or directory path expected to be present once the command completes successfully.
check_exitcode (bool, optional) – If True, assert that the process exit code is 0.
check_empty (bool, optional) – If True, also assert that
expected_outputis non-empty (file size > 0 / directory not empty).
- Returns:
{"out": <stdout as str>, "err": <stderr as str>}.- Return type:
- Raises:
AssertionError – If the exit code check or the expected-output check fails.
- os_helper.temporary_filename(suffix='', mode='wt', prefix='', delete=True, directory=None)[source]
Create a temporary file with a unique name that persists even after closing.
This context manager generates a temporary file with a unique name, which is optionally removed after use. It ensures that temporary files are managed safely and are cleaned up to prevent clutter or security issues.
- Parameters:
suffix (str, optional) – File suffix (e.g., “.txt”). Defaults to “”.
mode (str, optional) – Mode in which the file is opened (e.g., “wt” for writing text). Defaults to “wt”.
prefix (str, optional) – Prefix for the file name. Defaults to “”.
delete (bool, optional) – Whether to delete the file after exiting the context. Defaults to True.
directory (str or None, optional) – Parent directory to create the file in (default: the system temp dir). Use this when the temp file must sit next to other inputs — e.g. so a tool that resolves paths relative to the file still finds its siblings.
- Yields:
str – The name of the temporary file.
- Return type:
Example
>>> with temporary_filename(suffix=".txt") as temp_file: ... with open(temp_file, "wt") as fout: ... fout.write("Temporary content") ... # temp_file is automatically deleted after the block
- os_helper.temporary_folder(prefix='', delete=True)[source]
Create a temporary directory with a unique name that persists during the context.
This context manager generates a temporary directory with a unique name, which is optionally removed after use. It ensures that temporary directories are managed safely and are cleaned up to prevent clutter or security issues.
- Parameters:
- Yields:
str – The name of the temporary directory.
- Return type:
Example
>>> with temporary_folder(prefix="tempdir") as temp_dir: ... # Use the temporary directory ... with open(os.path.join(temp_dir, "file.txt"), "w") as f: ... f.write("Temporary content") ... # temp_dir and its contents are automatically deleted after the block
- os_helper.temporary_remote_file(upload_function, delete_function, *, prefix='', suffix='', from_local_file=None, checkfile_function=None, mode='wb', initial_content=None)[source]
Context manager that uploads a file to a remote location and guarantees deletion of the remote artifact when the context exits.
Two modes:
If
from_local_fileis given, that existing local file is uploaded as-is and left untouched on cleanup; only the remote copy is deleted.Otherwise, a uniquely named local temp file is created (optionally pre-populated with
initial_content), uploaded, and removed locally when the context manager exits.
Useful for staging files to S3/GCS/SFTP/anywhere when you only need them briefly: pass in the upload + delete callables for your storage backend and the helper handles the lifecycle.
- Parameters:
upload_function (Callable[[str], str]) – Called with a local file path; must return the remote path/URI.
delete_function (Callable[[str], None]) – Called with the remote path/URI to remove the remote artifact.
prefix (str, optional) – Prefix for the temporary file name (ignored when
from_local_fileis provided).suffix (str, optional) – File extension for the temporary file (with or without leading “.”).
from_local_file (str, optional) – Path to an existing local file to upload instead of creating one.
checkfile_function (Callable[[str], bool], optional) – Optional post-upload sanity check; must return True for success.
mode (str, optional) – Open mode used when writing
initial_contentto the temp file. Defaults to"wb".initial_content (bytes | str, optional) – Optional content written into the new temp file before upload. Type must match
mode(bytes for"wb", str for"wt").
- Yields:
str – The remote file path/URI returned by
upload_function.- Raises:
TypeError – If any supplied callable is not actually callable.
FileNotFoundError – If
from_local_fileis given but the path does not exist.RuntimeError – If
checkfile_functionis provided and returns False after upload.
- Return type:
Examples
>>> storage = {} >>> def upload(p): ... with open(p, "rb") as f: storage[p] = f.read() ... return p >>> def delete(r): ... storage.pop(r, None) >>> with temporary_remote_file(upload, delete, suffix=".bin", ... initial_content=b"hello") as remote: ... assert storage[remote] == b"hello" >>> # remote artifact is gone after the block
- os_helper.tic()[source]
Start (or restart) the implicit global stopwatch.
Returns the start timestamp so callers can pin a handle for nested or interleaved measurements:
>>> t_outer = tic() >>> # ... work ... >>> t_inner = tic() # implicit global now points at t_inner >>> # ... more work ... >>> toc(t_inner) # explicit handle works regardless of which tic() was last >>> toc(t_outer)
- os_helper.time2str(seconds, no_space=False)[source]
Convert a float number of seconds to a readable string, e.g. “1 hr 2 min 5 sec”.
- Parameters:
- Returns:
A human-readable time string.
- Return type:
Examples
>>> time2str(5400.0) "1 hr 30 min" >>> time2str(120.0) "2 min" >>> time2str(3661.0) "1 hr 1 min 1 sec"
- os_helper.toc(handle=None, *, log=False)[source]
Return seconds elapsed since the matching
tic()call.- Parameters:
- Returns:
Seconds elapsed (does not reset the timer — call
tic()again to restart).- Return type:
- Raises:
RuntimeError – If called with no handle and no prior
tic().
Examples
>>> tic() >>> # ... work ... >>> elapsed = toc()
- os_helper.unix()[source]
Determine if the current operating system is Unix-based (Linux or macOS).
- Returns:
True if the operating system is Unix-based (Linux or macOS), False otherwise.
- Return type:
- os_helper.untranslated(path)[source]
Every msgid in a
.powhose translation is empty.parse_po()drops these on purpose, so that an unfinished catalogue degrades to English. This reports them, which is what a test needs in order to fail on a half-finished translation instead of shipping one.- Parameters:
path (str or pathlib.Path) – The
.pofile to inspect.- Returns:
The untranslated msgids, in file order. The header entry (
msgid "") is not included — it legitimately carries metadata, not a translation.- Return type:
- os_helper.unzip_folder(zip_file_path, folder_path='', *, overwrite=False)[source]
Extract a
.ziparchive into a folder — the inverse ofzip_folder().Entries that would land outside
folder_pathare refused rather than written.zipfile.extractallhappily honours a member named../../etc/thingor an absolute path, so an archive from elsewhere can overwrite files anywhere the process can write. Since the point of this function is to unpack archives fetched over the network, that check is the reason it exists rather than a one-line call at each site. Symlink members are refused for the same reason: they are a second way out of the folder.- Parameters:
zip_file_path (str) – The archive to extract (must exist).
folder_path (str, optional) – Destination folder, created if missing. Defaults to the archive path without its
.zipsuffix.overwrite (bool, optional) – When
False(default), an existing non-empty destination is left untouched and returned as-is, which makes the call idempotent for a download-once-then-reuse cache.Trueextracts over it.
- Returns:
The destination folder.
- Return type:
- Raises:
AssertionError – If the archive does not exist.
ValueError – If a member would escape
folder_path, or is a symlink.
- os_helper.verbosity(level=None)[source]
Get or set the current root logger verbosity.
Called with no argument, returns the current verbosity as an integer. Called with an integer, updates the root logger (and its existing handlers) accordingly and returns the new effective verbosity.
Mapping (higher = more verbose):
>= 2→ DEBUG1→ INFO0→ WARNING-1→ ERROR<= -2→ CRITICAL
Values outside
[-2, 2]are clamped (e.g.verbosity(3)is treated as DEBUG, matching the convenience usage shown in the README).- Parameters:
level (int | None, optional) – New verbosity level to apply, or None to just read the current value.
- Returns:
Current (post-update, when setting) verbosity as an integer in the range
[-2, 2].- Return type:
Examples
>>> verbosity(2) # turn on DEBUG-level logging 2 >>> verbosity() # read current level 2
- os_helper.wall_timer()[source]
Measure real elapsed wall-clock time using
time.perf_counter().Use this when you want to know “how long did this take to run from the user’s perspective” — it includes I/O, sleeps, GPU waits, and subprocess time.
- Yields:
dict –
{"seconds": float, "milliseconds": float}, both fields populated when thewithblock exits.- Return type:
Examples
>>> with wall_timer() as t: ... time.sleep(0.05) >>> assert t["seconds"] >= 0.05
- os_helper.warning(msg, *args, **kwargs)[source]
Log a message at WARNING level via the dedicated
os_helperlogger.- Parameters:
msg (str) – The (possibly %-style) message template.
*args (Any) – Positional interpolation arguments forwarded to
logging.warning.**kwargs (Any) – Keyword options forwarded to
logging.warning.
- Return type:
None
- os_helper.windows()[source]
Determine if the current operating system is Windows.
- Returns:
True if the operating system is Windows, False otherwise.
- Return type:
- os_helper.write_po(path, entries, *, language, project)[source]
Write a
.pocatalogue, sorted by msgid so diffs stay readable.The header carries the charset, because a catalogue without one is read as ASCII by some tools and French is not ASCII.
- Parameters:
path (str or pathlib.Path) – Destination
.pofile; parent directories are created.entries (dict of str to str) –
{english_source: translation}. An empty translation is written as an emptymsgstr, which marks the entry as still to do rather than dropping it.language (str) – The catalogue’s language code, recorded in the header.
project (str) – The package the catalogue belongs to, recorded in the header.
- Returns:
The file written.
- Return type:
- os_helper.zip_folder(folder_path, zip_file_path='')[source]
Create a deflated
.ziparchive of a folder’s contents.Hidden files (names beginning with
.) are skipped. Paths inside the archive are stored relative tofolder_path.- Parameters:
- Raises:
AssertionError – If
folder_pathdoes not exist.- Return type:
None