Skip to content

Library-appropriate configuration: no argv parsing, config discovery#119

Merged
roed314 merged 2 commits into
roed314:mainfrom
roed-math:config-discovery
Jul 22, 2026
Merged

Library-appropriate configuration: no argv parsing, config discovery#119
roed314 merged 2 commits into
roed314:mainfrom
roed-math:config-discovery

Conversation

@roed-math

@roed-math roed-math commented Jul 22, 2026

Copy link
Copy Markdown

Ports the configuration redesign from LMFDB/lmfdb#7069 to psycodict itself, ahead of 1.0. Three checkout-era behaviors were wrong for a general-purpose PyPI library; each is the kind of breaking change 1.0 exists for.

1. Configuration() no longer parses the host program's argv

readargs auto-detected "running as a script" (hasattr(__main__, "__file__")) — so any script constructing PostgresDatabase() bare had psycodict's argparse run over its command line, and an unrecognized option printed psycodict's usage and raised SystemExit. readargs now defaults to False; a script that wants psycodict to own its command line passes readargs=True explicitly.

2. Config discovery — nothing is ever created in the cwd

With no explicit config_file, the path is discovered: $PSYCODICT_CONFIG./config.ini if it already exists~/.psycodict/config.ini. A missing file is auto-created only at an explicit location (defaults dict, CLI, env var) or the ~/.psycodict fallback — the current-directory candidate requires the file to already exist, so first use no longer sprinkles config.ini into whatever directory Python ran in. Existing cwd-config workflows keep working unchanged. The secrets file defaults to secrets.ini next to the resolved config file (same value as before for cwd configs).

When no home directory can be determined either (HOME unset and a uid with no passwd entry — expanduser returns the literal ~), the fallback raises a RuntimeError pointing at PSYCODICT_CONFIG instead of creating a literal ./~/.psycodict in the cwd; a homeless container with a configured location never consults the home directory.

3. The default slow-query log moves out of the cwd

The default slow_queries.log now resolves to the first usable of <config dir>/logs/ and ~/.psycodict/logs/ — probed by creating them and checking writability, since PostgresDatabase attaches its FileHandler at construction. The fallback means reading a configuration never requires write access beside it (a system-managed config in a read-only /etc/psycodict/ gets its default log under ~/.psycodict/logs/); if neither candidate is usable the plain name is kept (the historical cwd behavior). Any explicitly configured value — from the defaults dict or the config file — is used verbatim; only the bare default name is treated as "unconfigured", the same transparent redirect #7069 uses. Callers supplying their own parser are exempt from the redirect entirely.

In passing

postgresql_dbname was the one option whose default ignored the defaults dictionary (hardcoded "lmfdb"); it now honors defaults.get("postgresql_dbname", "lmfdb").

Downstream compatibility

LMFDB and seminars both subclass Configuration with their own parser, their own config_file default anchored to their checkout, and explicit readargs/writeargstofile — none of the changed defaults reach them, and the log redirect is additionally gated to the builtin parser. The downstream CI jobs verify this mechanically.

Tests

tests/test_config.py grows a discovery section (env var / cwd / home-fallback precedence, explicit-beats-env, nothing-created-in-cwd), slow-log location coverage (default redirected + written file keeps the plain name, explicit values respected, config-file values respected, custom parsers exempt), the argv regression (a fake __main__ with __file__ and hostile argv → no SystemExit), the postgresql_dbname fix, and — from review — the homeless-container case (no HOME, no passwd entry → clear RuntimeError, no literal ./~; a discoverable config still works) and the read-only-config-directory case (0555 config dir parses fine, log falls back to ~/.psycodict/logs; with no home either, the plain name survives). Full suite 833 passed / 36 skipped locally (PG18, psycopg 3.3.4); ruff clean.

#113's README quickstart and CHANGELOG describe the old cwd behavior; I'm updating that branch to match in tandem.

🤖 Generated with Claude Code

Porting the configuration redesign from LMFDB/lmfdb#7069 to psycodict
itself, ahead of the 1.0 release.  Three checkout-era behaviors were wrong
for a general-purpose library:

- Configuration() auto-detected "running as a script" and then parsed the
  host program's command line; an option psycodict did not recognize meant
  argparse printed psycodict's usage and raised SystemExit.  readargs now
  defaults to False everywhere -- the command line belongs to the host
  program unless it explicitly hands it over.

- The configuration file defaulted to ./config.ini and was created there
  on first use, sprinkling config files into whatever directory Python
  first ran in.  With no explicit location the file is now discovered:
  $PSYCODICT_CONFIG, then ./config.ini if it already exists, then
  ~/.psycodict/config.ini -- and only the latter two explicit locations
  are auto-created, never the working directory.  The secrets file
  defaults to secrets.ini next to the resolved configuration file.

- The default slow_queries.log landed in the working directory; the
  default name now resolves to a logs directory next to the configuration
  file (created eagerly, since PostgresDatabase attaches its FileHandler
  at construction).  Any explicitly configured value is used verbatim.

Also fixes postgresql_dbname, the one option whose default ignored the
defaults dictionary.

Downstream is unaffected: LMFDB and seminars both subclass Configuration
with their own parser, their own config_file default and explicit
readargs, so none of the changed defaults reach them; the log redirect is
additionally gated to the builtin parser.  The downstream CI jobs verify
this mechanically.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
roed-math pushed a commit to roed-math/psycodict that referenced this pull request Jul 22, 2026
README quickstart: config.ini is now discovered ($PSYCODICT_CONFIG, then an
existing ./config.ini, then ~/.psycodict/config.ini) rather than created in
the working directory.  CHANGELOG: breaking-changes entry for the readargs
flip, the discovery and the log/secrets relocation, plus the
postgresql_dbname defaults fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two review findings, both reproduced:

- os.path.expanduser("~") returns the literal ~ when HOME is unset and
  the uid has no passwd entry (a common container setup), so the home
  fallback created ./~/.psycodict in the working directory -- exactly the
  pollution the discovery exists to prevent.  psycodict_home() now
  requires the expansion to be absolute and otherwise raises a
  RuntimeError pointing at PSYCODICT_CONFIG.  A discoverable
  configuration (env var or ./config.ini) never consults the home
  directory, so homeless containers with a configured location work.

- The slow-log redirect eagerly created <config dir>/logs, so merely
  reading a configuration file in a directory without write access (a
  system-managed /etc/psycodict/config.ini, say) raised PermissionError.
  The default log directory is now the first usable of <config dir>/logs
  and ~/.psycodict/logs -- probed by creating them, checked writable --
  and when neither is usable the plain default name is kept (the
  historical cwd behavior), so reading a configuration never requires
  write access beside it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@roed314
roed314 merged commit 8758520 into roed314:main Jul 22, 2026
16 checks passed
roed314 added a commit that referenced this pull request Jul 22, 2026
The defaults key list from #119 and the warnings-as-errors docs build
from #122 first met when both merged: a nested bullet list needs blank
lines around it in reST, so the docs job (and the Read the Docs build)
failed on main with 'Unexpected indentation'.  Two blank lines fix it,
and the key list now renders as an actual nested list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
roed-math pushed a commit to roed-math/psycodict that referenced this pull request Jul 22, 2026
README quickstart: config.ini is now discovered ($PSYCODICT_CONFIG, then an
existing ./config.ini, then ~/.psycodict/config.ini) rather than created in
the working directory.  CHANGELOG: breaking-changes entry for the readargs
flip, the discovery and the log/secrets relocation, plus the
postgresql_dbname defaults fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
roed-math pushed a commit to roed-math/psycodict that referenced this pull request Jul 22, 2026
Rebased onto main, which absorbed everything this PR used to anticipate;
the reconciliation:

- Version: 1.0.0 is now set in psycodict.__init__.__version__, the single
  source of truth roed314#121 established -- pyproject keeps main's dynamic
  version, SPDX license string and >=3.9 floor, and this PR's classifiers
  drop the License:: entry (setuptools >= 77 rejects it next to an SPDX
  string) and the 3.8 entry.

- README: the rewrite now carries the Versioning.md pointer (roed314#123) in its
  Documentation list, which switches to relative links -- on the Sphinx
  site (roed314#122) those resolve to the copied guide pages, and they still
  render on GitHub; CHANGELOG and LICENSE stay absolute since neither is
  on the site.  Supported Python is 3.9+; blob/master URLs become
  blob/main.

- CHANGELOG: entries for the late wave -- sum/random honoring saving
  (roed314#118) and the 3.9 floor (roed314#121) under breaking changes; the docs site
  (roed314#122), Versioning.md/CONTRIBUTING/SECURITY and __version__ (roed314#121)
  under added; reload metafile+resort (roed314#114), update_from_file stats
  publication (roed314#115), copy_dumps delimiter escaping (roed314#116) under fixed;
  config.ini untracking (roed314#120), this release workflow and CITATION.cff
  (roed314#124) under infrastructure.

- release.yml: the flow notes now say bump __init__.__version__ and merge
  to main.

- Fixes the docs build on main, broken by the roed314#119 x roed314#122 crossing (roed314#119
  merged after roed314#122's last CI run): the Configuration docstring's nested
  defaults list needed blank lines to be valid RST under autodoc, and the
  README's relative LICENSE link had no target on the site.

Suite 857 passed / 36 skipped; ruff clean; sphinx -W clean; python -m
build + twine check --strict pass with Version 1.0.0,
License-Expression GPL-2.0-or-later, Requires-Python >=3.9 and no
License classifier in the wheel metadata.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants