tinycommons
uv add tinycommons # or tinycommons[rich]
The small utilities that got copy-pasted into every repo — setup_logging,
load_env, the ten-line sqlite open, JSON-file-as-database, and slugs.
Extracted once, from seven repos (tiktok-lazy-follower, get_cool_event,
dialogue-lens, finance-insights, jobs, dental-parser, chat-watch).
Python ≥3.12. Stdlib only. rich is an optional extra used by logsetup's
console sink and nothing else — and even then it is imported inside the function
and falls back to a plain StreamHandler, so a stdlib-only consumer can install
tinycommons bare.
| module | what it is |
|---|---|
tinycommons.logsetup |
setup_logging — per-run log file + optional console, on a per-package logger tree |
tinycommons.env |
load_env — dependency-free KEY=VALUE .env reader |
tinycommons.db |
open_sqlite_db / sqlite_session — Row factory, idempotent schema, mkdir |
tinycommons.json_store |
load_or_default / save_atomic / merge_keep_nonempty |
tinycommons.text |
safe_filename / slugify |
Behaviour
The normative behaviour of every module lives in openspec/specs/ — 22
requirements over 68 scenarios, each one carrying the reason it exists and, where
there was one, the bug in the original copy that put it there. Those specs are
the baseline; changes are proposed against them.
| capability | spec |
|---|---|
tinycommons.logsetup |
openspec/specs/logsetup/spec.md |
tinycommons.env |
openspec/specs/env/spec.md |
tinycommons.db |
openspec/specs/db/spec.md |
tinycommons.json_store |
openspec/specs/json-store/spec.md |
tinycommons.text |
openspec/specs/text/spec.md |
openspec list --specs # requirement counts
openspec show logsetup # read one
openspec validate --specs --strict # check them
The short version, if you only read one thing: nothing is configured at import
time, a corrupt file raises instead of reading as empty, a save is atomic, a
re-scrape never overwrites a non-empty field, and rich is optional at runtime
rather than only at install time.
Usage
from tinycommons import setup_logging
# tiktok-lazy-follower / get_cool_event, verbatim behaviour:
log_path = setup_logging("tlf", cfg.logs_dir) # rich console + DEBUG file
# dialogue-lens: files only, persistent + per-run
setup_logging("dialogue_lens", "logs", console_level=None,
persistent_file="dialogue-lens.log", run_prefix="run-")
# jobs: root logger, plain console, plus jobspy's non-propagating loggers
setup_logging("", "logs", rich=False, run_prefix="run_", attach=["JobSpy:*"])
Production defaults (all stdlib, each with an opt-out kwarg): the persistent file
is a RotatingFileHandler (persistent_max_bytes=10 MiB, persistent_backups=5);
uncaught exceptions in the main thread (CRITICAL) and worker threads (ERROR) are
logged with their traceback and then handed to the previous hook
(catch_uncaught=True); warnings.warn lands in the file
(capture_warnings=True); and values of *TOKEN* / *SECRET* / *KEY* /
*PASSWORD* / *HASH* env vars (8+ chars) and Bearer <token> are masked as
*** in every handler (redact=True; the env is read once per setup call).
from tinycommons import load_env, open_sqlite_db, sqlite_session
env = load_env(".env", {"WORK_TZ": "Asia/Almaty"})
SCHEMA = "CREATE TABLE IF NOT EXISTS videos (id TEXT PRIMARY KEY, author TEXT);"
with sqlite_session("data/cache.db", SCHEMA) as conn: # commits on clean exit
conn.execute("INSERT OR IGNORE INTO videos VALUES (?, ?)", ("id1", "bob"))
conn = open_sqlite_db("cache.db", SCHEMA) # caller closes
from tinycommons import load_or_default, merge_keep_nonempty, safe_filename, save_atomic
clinics = {c["link_2gis"]: c for c in load_or_default("data/clinics.json", [])}
fresh = merge_keep_nonempty(clinics.get(link, {}), scraped,
always=("rating", "review_count"))
save_atomic("data/clinics.json", [*clinics.values()])
images_dir = IMAGES / safe_filename(fresh["name"]) # max_len=50, sep="_"
API
logsetup: setup_logging(logger_name, logs_dir="logs", *, console_level=INFO,
file_level=DEBUG, rich=True, console=None,
persistent_file=False, attach=(), run_prefix="",
reconfigure=False) -> Path
run_log_path(logger_name) -> Path | None
FILE_FORMAT, RUN_STAMP
env: load_env(path, defaults=None, *, override_os_environ=False) -> dict[str, str]
db: open_sqlite_db(path="cache.db", schema_sql="") -> sqlite3.Connection
sqlite_session(path="cache.db", schema_sql="") # contextmanager
json_store: load_or_default(path, default=None) -> Any
save_atomic(path, data, *, indent=2) -> Path
merge_keep_nonempty(old, new, *, always=()) -> dict
text: safe_filename(name, *, max_len=50, sep="_") -> str # keeps Unicode + case
slugify(text, *, max_len=0, sep="-", fallback="x") -> str # ascii lowercase
Migration notes
- Per-run filenames are unified on
<run_prefix><YYYY-MM-DD-HH-MM-SS>.log(tlf/gce's format). dialogue-lens (run-%Y%m%d-%H%M%S.log) and jobs (run_%Y-%m-%d_%H%M%S.log) getrun-/run_viarun_prefixbut their stamp punctuation changes. Nothing parses these names. setup_loggingtakes a logger name and a directory, not a config object. tlf passed its wholeConfig; callsetup_logging("tlf", cfg.logs_dir).- dialogue-lens'
setup_loggingreturned aLoggerand logged a session banner; this returns the per-runPath. Uselogging.getLogger(name)and log your ownargvline — the banner is app policy, not logging setup. - finance-insights keeps its own
logsetupand is not a consumer: it is deliberately rich-free withRotatingFileHandlerplus a secondllm_auditlogger tree, i.e. a different design, not a parameterisation. load_envno longer carries dialogue-lens'DEFAULTSdict. Those values (WORK_TZ,WORK_START, …) are that app's domain config; pass them in.merge_keep_nonemptyreturns a new dict; dental-parser mutated theexistingdict and reassigned. Assign the result.load_or_defaultraises on corrupt JSON where dental-parser'sexcept: passreturned empty. A consumer that truly wants the old behaviour must catchjson.JSONDecodeErrorexplicitly.sanitize_name→safe_filename,find_clinic_directory's inlined copy should call the same function. Output is unchanged for existing directory names.
Tests
cd pylibs && uv run --package tinycommons --extra dev pytest tinycommons/tests -q
94 tests, no network, no real logs/ directory (everything goes to tmp_path).
The rich-absent branch is covered by monkeypatching builtins.__import__, so the
stdlib fallback is tested with rich installed.
Metadata
Release files for tinycommons 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tinycommons-0.2.0.tar.gz | 20.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tinycommons-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 36.3 kB
Release files / tinycommons-0.2.0.tar.gz
| Download URL | tinycommons-0.2.0.tar.gz |
|---|---|
| Size | 20.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ed0d66c712de9fda937a98e05d6daffc65f20be465c8b7005a27841c3f42db98
|
|
BLAKE2b-256 checksum How to use checksums |
73d39840a41bed0fae62e883c897e4e93abb5b25bb011b923f6ff67cc911a58a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logRelease files / tinycommons-0.2.0-py3-none-any.whl
| Download URL | tinycommons-0.2.0-py3-none-any.whl |
|---|---|
| Size | 16.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c65ce9bac7743f4d8494208176fcc643854f0590eedebc1f7414d34bf42acb9c
|
|
BLAKE2b-256 checksum How to use checksums |
a863b8160527a4d76f936c080c886f6a9993c5d3ab386e5e08afa5eab0cd04d0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency log