Library-appropriate configuration: no argv parsing, config discovery#119
Merged
Conversation
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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 argvreadargsauto-detected "running as a script" (hasattr(__main__, "__file__")) — so any script constructingPostgresDatabase()bare had psycodict's argparse run over its command line, and an unrecognized option printed psycodict's usage and raisedSystemExit.readargsnow defaults toFalse; a script that wants psycodict to own its command line passesreadargs=Trueexplicitly.2. Config discovery — nothing is ever created in the cwd
With no explicit
config_file, the path is discovered:$PSYCODICT_CONFIG→./config.iniif it already exists →~/.psycodict/config.ini. A missing file is auto-created only at an explicit location (defaults dict, CLI, env var) or the~/.psycodictfallback — the current-directory candidate requires the file to already exist, so first use no longer sprinklesconfig.iniinto whatever directory Python ran in. Existing cwd-config workflows keep working unchanged. The secrets file defaults tosecrets.ininext to the resolved config file (same value as before for cwd configs).When no home directory can be determined either (
HOMEunset and a uid with no passwd entry —expanduserreturns the literal~), the fallback raises aRuntimeErrorpointing atPSYCODICT_CONFIGinstead of creating a literal./~/.psycodictin 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.lognow resolves to the first usable of<config dir>/logs/and~/.psycodict/logs/— probed by creating them and checking writability, sincePostgresDatabaseattaches itsFileHandlerat 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_dbnamewas the one option whose default ignored thedefaultsdictionary (hardcoded"lmfdb"); it now honorsdefaults.get("postgresql_dbname", "lmfdb").Downstream compatibility
LMFDB and seminars both subclass
Configurationwith their own parser, their ownconfig_filedefault anchored to their checkout, and explicitreadargs/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.pygrows 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 → noSystemExit), thepostgresql_dbnamefix, and — from review — the homeless-container case (noHOME, no passwd entry → clearRuntimeError, 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