execsql2 2.12.1__tar.gz → 2.12.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- execsql2-2.12.2/.claude/agents/liaison.md +107 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/CHANGELOG.md +12 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/CLAUDE.md +1 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/PKG-INFO +2 -1
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/about/divergence.md +1 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/getting-started/installation.md +4 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/reference/metacommands.md +3 -1
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/reference/security.md +59 -1
- {execsql2-2.12.1 → execsql2-2.12.2}/pyproject.toml +3 -2
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exceptions.py +2 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/metacommands/control.py +1 -1
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/metacommands/test_assert.py +14 -1
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/test_exceptions.py +1 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/uv.lock +1 -1
- {execsql2-2.12.1 → execsql2-2.12.2}/.claude/agents/dba.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.claude/agents/herald.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.claude/agents/inspector.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.claude/agents/oracle.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.claude/agents/patcher.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.claude/agents/qa.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.claude/agents/scribe.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.claude/commands/code-oracle.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.claude/commands/migrate.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.claude/commands/review-changes.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.claude/commands/test-module.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.claude/commands/update-changelog.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.claude/commands/where-is.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.claude/project_context.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.claude/state/status.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.github/workflows/ci-cd.yml +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.gitignore +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.pre-commit-config.yaml +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.python-version +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/.readthedocs.yaml +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/CONTRIBUTING.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/LICENSE.txt +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/NOTICE +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/README.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/SECURITY.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/about/contributors.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/about/copyright.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/api/cli.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/api/db.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/api/exporters.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/api/importers.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/api/index.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/api/metacommands.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/dev/architecture.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/getting-started/syntax.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/guides/debugging.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/guides/documentation.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/guides/encoding.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/guides/examples.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/guides/formatter.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/guides/logging.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/guides/usage.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/actions.png +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/actions2.png +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/checkboxes.png +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/connect.b64 +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/connect.png +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/create_conf.png +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/entry_form.png +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/execsql_console.png +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/fatals.png +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/logo_small.png +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/unmatched.png +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/index.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/reference/configuration.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/docs/reference/substitution_vars.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/justfile +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/__main__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/cli/help.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/cli/lint.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/cli/run.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/config.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/constants.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/db/access.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/db/base.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/db/dsn.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/db/duckdb.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/db/factory.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/db/firebird.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/db/mysql.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/db/oracle.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/db/postgres.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/db/sqlite.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/db/sqlserver.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/debug/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/debug/repl.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/delimited.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/duckdb.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/markdown.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/ods.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/templates.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/xls.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/xlsx.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/yaml.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/format.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/gui/base.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/gui/console.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/gui/tui.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/importers/base.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/metacommands/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/metacommands/debug.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/metacommands/dispatch.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/metacommands/io_export.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/metacommands/io_fileops.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/models.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/parser.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/py.typed +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/script/control.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/script/engine.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/script/variables.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/state.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/types.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/utils/errors.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/utils/fileio.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/templates/README.md +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/templates/config_settings.sqlite +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/templates/execsql.conf +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/templates/make_config_db.sql +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/templates/md_compare.sql +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/templates/md_glossary.sql +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/templates/md_upsert.sql +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/templates/pg_compare.sql +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/templates/pg_glossary.sql +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/templates/pg_upsert.sql +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/templates/script_template.sql +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/templates/ss_compare.sql +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/templates/ss_glossary.sql +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/templates/ss_upsert.sql +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/cli/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/cli/test_cli.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/cli/test_lint.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/cli/test_ping.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/cli/test_profile.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/conftest.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/db/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/db/test_base.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/db/test_duckdb.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/db/test_factory.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/db/test_postgres.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/test_base.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/test_db.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/test_delimited.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/test_json.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/gui/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/gui/test_backends.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/importers/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/integration/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/integration/conftest.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/integration/test_postgres.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/metacommands/test_breakpoint.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/metacommands/test_io_export.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/metacommands/test_io_import.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/test_config.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/test_config_data.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/test_constants.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/test_engine.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/test_error_messages.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/test_format.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/test_mail.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/test_models.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/test_package.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/test_parser.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/test_registry.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/test_script.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/test_state.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/test_types.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/utils/__init__.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/utils/test_auth.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/utils/test_errors.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/utils/test_regex.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/utils/test_strings.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/utils/test_timer.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.12.1 → execsql2-2.12.2}/zensical.toml +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: The Liaison
|
|
3
|
+
description: Integration specialist for pg-upsert and execsql2. Tracks the pg-upsert codebase (../pg-upsert), designs the optional dependency integration, and owns the UPSERT metacommand. Reads both codebases — writes only in execsql2.
|
|
4
|
+
model: sonnet
|
|
5
|
+
color: yellow
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are a senior Python engineer who specializes in integrating the **pg-upsert** library with the **execsql2** project. You are the bridge between two codebases maintained by the same author:
|
|
9
|
+
|
|
10
|
+
- **execsql2** — `/Users/cgrant/GitHub/geocoug/execsql/` (this repo)
|
|
11
|
+
- **pg-upsert** — `/Users/cgrant/GitHub/geocoug/pg-upsert/` (sibling repo)
|
|
12
|
+
|
|
13
|
+
Your job is to keep the integration plan current, design the `UPSERT` metacommand, and ensure changes in either codebase don't break the integration path.
|
|
14
|
+
|
|
15
|
+
## Expertise
|
|
16
|
+
|
|
17
|
+
You have deep, working knowledge of:
|
|
18
|
+
|
|
19
|
+
**pg-upsert internals:** The `PgUpsert` class (QA checks: null, PK, FK, check constraint validation), `PostgresDB` connection wrapper, the staging-to-base upsert workflow, topological table ordering, the `ups_control` temporary table, and the `psycopg2.sql` parameterized SQL generation patterns.
|
|
20
|
+
|
|
21
|
+
**execsql2 extension points:** The metacommand dispatch system (`MetaCommandList`, `build_dispatch_table()`, `x_*` handlers), `DatabasePool` connection management, substitution variables (`SubVarSet`), transaction/autocommit model, the `PostgresDatabase` adapter, and the existing upsert SQL templates in `templates/pg_upsert.sql`.
|
|
22
|
+
|
|
23
|
+
**Integration patterns:** Optional dependency detection (`importlib.util.find_spec`), connection sharing between libraries, adapter patterns for bridging different transaction models, and graceful degradation when an optional package is not installed.
|
|
24
|
+
|
|
25
|
+
______________________________________________________________________
|
|
26
|
+
|
|
27
|
+
## First Actions (always, before doing any work)
|
|
28
|
+
|
|
29
|
+
1. **Read `.claude/project_context.md`** — load the execsql2 module layout and architectural overview.
|
|
30
|
+
2. **Read your briefing** if one exists at `.claude/comms/briefings/liaison-*.md` — follow the DBA's specific instructions.
|
|
31
|
+
3. **Read the integration plan** at `../pg-upsert/.claude/plans/refactor-and-execsql-integration.md` — this is the canonical plan. Understand the current phase and what's been decided.
|
|
32
|
+
4. **Check pg-upsert's current state** — read `../pg-upsert/src/pg_upsert/upsert.py` and `../pg-upsert/src/pg_upsert/postgres.py` to understand the latest API surface. pg-upsert is under active refactoring; the API may have changed since the plan was written.
|
|
33
|
+
5. **Check execsql2's metacommand system** — read `src/execsql/metacommands/dispatch.py` to understand registration patterns.
|
|
34
|
+
|
|
35
|
+
______________________________________________________________________
|
|
36
|
+
|
|
37
|
+
## Key Design Decisions (established)
|
|
38
|
+
|
|
39
|
+
These have been agreed upon. Do not revisit unless the human asks:
|
|
40
|
+
|
|
41
|
+
1. **pg-upsert is an optional dependency** — execsql2 must work without it installed. The `UPSERT` metacommand raises a clear error if pg-upsert is missing.
|
|
42
|
+
2. **Connection sharing** — the metacommand passes execsql's existing `psycopg2` connection to `PgUpsert(conn=...)`. No second connection.
|
|
43
|
+
3. **Results map to substitution variables** — upsert stats (rows updated, inserted, QA errors) are exposed as `!!$UPSERT_*!!` substitution variables.
|
|
44
|
+
4. **execsql's transaction model governs** — the metacommand respects execsql's `AUTOCOMMIT ON/OFF` state, not pg-upsert's `do_commit` flag.
|
|
45
|
+
5. **No Tkinter dependency** — the interactive GUI is not used when called from execsql. QA results go to execsql's logging/debug REPL instead.
|
|
46
|
+
|
|
47
|
+
______________________________________________________________________
|
|
48
|
+
|
|
49
|
+
## pg-upsert API Surface (reference)
|
|
50
|
+
|
|
51
|
+
The integration depends on these pg-upsert interfaces. If any change, the integration plan must be updated.
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
# Core class — this is what the metacommand will call
|
|
55
|
+
PgUpsert(
|
|
56
|
+
conn=psycopg2_connection, # Shared from execsql
|
|
57
|
+
tables=["t1", "t2"],
|
|
58
|
+
staging_schema="staging",
|
|
59
|
+
base_schema="public",
|
|
60
|
+
do_commit=False, # execsql controls commits
|
|
61
|
+
interactive=False, # No GUI from execsql
|
|
62
|
+
upsert_method="upsert", # "upsert" | "update" | "insert"
|
|
63
|
+
exclude_cols=["col1"],
|
|
64
|
+
exclude_null_check_cols=["col2"],
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Methods the metacommand will call
|
|
68
|
+
.qa_all() # Run all QA checks, returns self
|
|
69
|
+
.upsert_all() # Run upsert on all tables, returns self
|
|
70
|
+
.commit() # Commit/rollback based on do_commit + qa_passed
|
|
71
|
+
.run() # qa_all() → upsert_all() → commit()
|
|
72
|
+
|
|
73
|
+
# State the metacommand will read
|
|
74
|
+
.qa_passed # bool — did all QA checks pass?
|
|
75
|
+
.control_table # str — name of temp table with per-table results
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
______________________________________________________________________
|
|
79
|
+
|
|
80
|
+
## What to Produce
|
|
81
|
+
|
|
82
|
+
Depending on the task, you may produce:
|
|
83
|
+
|
|
84
|
+
1. **Integration status reports** — what's changed in pg-upsert, what that means for execsql integration
|
|
85
|
+
2. **Metacommand design** — regex patterns, handler signatures, SQL syntax for `UPSERT` metacommand
|
|
86
|
+
3. **Implementation code** — `src/execsql/metacommands/upsert.py` and dispatch registration
|
|
87
|
+
4. **Compatibility notes** — API changes in pg-upsert that require adaptation
|
|
88
|
+
5. **Test specifications** — what the QA agent should test for the integration
|
|
89
|
+
|
|
90
|
+
______________________________________________________________________
|
|
91
|
+
|
|
92
|
+
## Syndicate Protocol
|
|
93
|
+
|
|
94
|
+
When working as part of the SQL Syndicate:
|
|
95
|
+
|
|
96
|
+
1. Read your briefing from `.claude/comms/briefings/liaison-*.md`
|
|
97
|
+
2. Do your research across both codebases
|
|
98
|
+
3. Write your report to `.claude/comms/reports/liaison-{YYYY-MM-DD}.md`
|
|
99
|
+
4. Write integration artifacts to `.claude/plans/` (for design) or `.claude/patches/` (for implementation)
|
|
100
|
+
|
|
101
|
+
## Constraints
|
|
102
|
+
|
|
103
|
+
- **Writes only in execsql2.** You may read `../pg-upsert/` freely but never modify files there.
|
|
104
|
+
- **pg-upsert is a moving target.** Always re-read the current pg-upsert source before making claims about its API — don't rely on cached knowledge.
|
|
105
|
+
- **Optional means optional.** Never add pg-upsert to execsql2's required dependencies. Use `extras_require` / optional dependency groups.
|
|
106
|
+
- **Preserve existing upsert templates.** The SQL templates in `templates/pg_upsert.sql` must continue to work. The metacommand is an alternative, not a replacement.
|
|
107
|
+
- Follow all execsql2 project constraints from CLAUDE.md (ruff, Python 3.10+, coverage floor, changelog, docs, divergence tracking).
|
|
@@ -13,6 +13,18 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.12.2] - 2026-04-02
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Documentation: keyring setup guide for headless Linux servers (encrypted and plaintext file backends) in the Security reference page, with a cross-reference from the Installation page.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- `ASSERT` failures now report `**** Assertion failed.` instead of `**** Error in metacommand.` to distinguish intentional script-level checks from actual metacommand errors.
|
|
25
|
+
|
|
26
|
+
______________________________________________________________________
|
|
27
|
+
|
|
16
28
|
## [2.12.1] - 2026-04-02
|
|
17
29
|
|
|
18
30
|
### Changed
|
|
@@ -22,6 +22,7 @@ A multi-agent system where specialized agents collaborate to improve, extend, de
|
|
|
22
22
|
- `.claude/docs-drafts/` — Scribe's documentation drafts
|
|
23
23
|
- `.claude/releases/` — Herald's release notes and changelog entries
|
|
24
24
|
- `.claude/state/` — Shared state (current phase, active task, agent status)
|
|
25
|
+
- `../pg-upsert/.claude/plans/` — pg-upsert integration plan (read by The Liaison)
|
|
25
26
|
|
|
26
27
|
## Communication Protocol
|
|
27
28
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.12.
|
|
3
|
+
Version: 2.12.2
|
|
4
4
|
Summary: Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables.
|
|
5
|
+
Project-URL: Homepage, https://execsql2.readthedocs.io
|
|
5
6
|
Project-URL: Repository, https://github.com/geocoug/execsql
|
|
6
7
|
Project-URL: Issues, https://github.com/geocoug/execsql/issues
|
|
7
8
|
Author-email: Dreas Nielsen <cortice@tutanota.com>
|
|
@@ -174,6 +174,7 @@ All 33 mutable runtime globals in `state.py` have been consolidated into a `Runt
|
|
|
174
174
|
|
|
175
175
|
- **Exception hierarchy** — All custom exceptions inherit from `ExecSqlError`, enabling `except ExecSqlError` to catch any execsql-originated error.
|
|
176
176
|
- **Exception chaining** — All `raise` statements inside `except` blocks preserve the original traceback via `from`.
|
|
177
|
+
- **ASSERT error type** — `ASSERT` failures now use a dedicated `"assert"` error type that produces `**** Assertion failed.` instead of `**** Error in metacommand.`. This distinguishes intentional script-level checks from actual metacommand errors. Upstream did not have `ASSERT`.
|
|
177
178
|
|
|
178
179
|
______________________________________________________________________
|
|
179
180
|
|
|
@@ -30,3 +30,7 @@ pip install "execsql2[all]" # Everything
|
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
In addition to the *execsql* program itself, additional Python libraries may need to be installed to use *execsql* with specific types of DBMSs and spreadsheets. The additional libraries that may be needed are listed in the [Requirements](requirements.md#requirements) section.
|
|
33
|
+
|
|
34
|
+
!!! tip "Keyring on headless Linux"
|
|
35
|
+
|
|
36
|
+
If you install `execsql2[auth]` on a headless Linux server (no desktop environment), the keyring backend needs manual configuration. See [Keyring Platform Setup](../reference/security.md#keyring_setup) for instructions.
|
|
@@ -56,7 +56,9 @@ ASSERT <condition> "<failure message>"
|
|
|
56
56
|
ASSERT <condition> '<failure message>'
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
Evaluates `<condition>` using the same expression engine as [IF](#if_cmd). If the condition is `True`, execution continues silently (and the result is written to the log). If the condition is `False`, an error is raised with the provided failure message. If no message is supplied, the default message is `Assertion failed: <condition>`.
|
|
59
|
+
Evaluates `<condition>` using the same expression engine as [IF](#if_cmd). If the condition is `True`, execution continues silently (and the result is written to the log). If the condition is `False`, an assertion error is raised with the provided failure message. If no message is supplied, the default message is `Assertion failed: <condition>`.
|
|
60
|
+
|
|
61
|
+
A failed assertion produces `**** Assertion failed.` (not `**** Error in metacommand.`) to make it clear that the script's own check caught a problem, not that execsql encountered an internal error.
|
|
60
62
|
|
|
61
63
|
When [HALT_ON_METACOMMAND_ERROR](#config) is `ON` (the default), a failed assertion halts the script. When it is `OFF`, execution continues after the failure is logged.
|
|
62
64
|
|
|
@@ -29,7 +29,7 @@ When execsql needs a database password and none is stored or configured, it prom
|
|
|
29
29
|
|
|
30
30
|
### OS credential store (keyring)
|
|
31
31
|
|
|
32
|
-
When the optional `keyring` package is installed (`pip install execsql2[auth]`), execsql checks the OS credential store before prompting. After a successful interactive prompt, the password is automatically stored for future use.
|
|
32
|
+
When the optional `keyring` package is installed (`pip install execsql2[auth]`), execsql checks the OS credential store before prompting. After a successful interactive prompt, the password is automatically stored for future use. Keyring service names follow the pattern:
|
|
33
33
|
|
|
34
34
|
```text
|
|
35
35
|
execsql/<db_type>/<server>/<database>
|
|
@@ -37,6 +37,64 @@ execsql/<db_type>/<server>/<database>
|
|
|
37
37
|
|
|
38
38
|
To disable keyring integration, set `use_keyring = No` in the `[connect]` section of `execsql.conf`.
|
|
39
39
|
|
|
40
|
+
#### Platform setup { #keyring_setup }
|
|
41
|
+
|
|
42
|
+
**macOS** — Works out of the box. Keyring uses the macOS Keychain automatically.
|
|
43
|
+
|
|
44
|
+
**Windows** — Works out of the box. Keyring uses the Windows Credential Manager automatically.
|
|
45
|
+
|
|
46
|
+
**Linux (desktop with GNOME/KDE)** — Works out of the box if a SecretService provider (GNOME Keyring or KWallet) is running.
|
|
47
|
+
|
|
48
|
+
**Linux (headless / remote / server)** — No secret service is typically available, so keyring silently fails to store passwords. You need to configure a file-based backend. There are two options:
|
|
49
|
+
|
|
50
|
+
##### Option A: Encrypted file backend (recommended) { #keyring_encrypted }
|
|
51
|
+
|
|
52
|
+
Passwords are stored encrypted on disk. Requires a master password the first time keyring is used per session.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install keyrings.alt pycryptodome
|
|
56
|
+
mkdir -p ~/.config/python_keyring
|
|
57
|
+
cat > ~/.config/python_keyring/keyringrc.cfg << 'EOF'
|
|
58
|
+
[backend]
|
|
59
|
+
default-keyring=keyrings.alt.file.EncryptedKeyring
|
|
60
|
+
EOF
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The encrypted keyring file is stored at `~/.local/share/python_keyring/crypted_pass.cfg`. You will be prompted for a master password once per session (e.g., when you first run execsql after logging in).
|
|
64
|
+
|
|
65
|
+
##### Option B: Plaintext file backend (no prompts) { #keyring_plaintext }
|
|
66
|
+
|
|
67
|
+
Passwords are stored in plain text on disk. No master password is needed — execsql will never prompt for a password after the first successful entry.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install keyrings.alt
|
|
71
|
+
mkdir -p ~/.config/python_keyring
|
|
72
|
+
cat > ~/.config/python_keyring/keyringrc.cfg << 'EOF'
|
|
73
|
+
[backend]
|
|
74
|
+
default-keyring=keyrings.alt.file.PlaintextKeyring
|
|
75
|
+
EOF
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Passwords are stored at `~/.local/share/python_keyring/keyring_pass.cfg`. Restrict access to this file:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
chmod 600 ~/.local/share/python_keyring/keyring_pass.cfg
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
!!! warning "Plaintext storage"
|
|
85
|
+
|
|
86
|
+
The plaintext backend stores passwords without encryption. Only use this on machines where the filesystem is already secured (encrypted disk, single-user access, restricted permissions). Prefer the encrypted backend when possible.
|
|
87
|
+
|
|
88
|
+
##### Verifying keyring setup { #keyring_verify }
|
|
89
|
+
|
|
90
|
+
After configuring a backend, verify it works:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
python -c "import keyring; keyring.set_password('test', 'user', 'pw'); print(keyring.get_password('test', 'user'))"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This should print `pw` without errors. If it does, the next time execsql prompts for a database password, it will store it and skip the prompt on future runs.
|
|
97
|
+
|
|
40
98
|
### `enc_password` in execsql.conf
|
|
41
99
|
|
|
42
100
|
!!! warning "Obfuscation only — not encryption"
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "execsql2"
|
|
7
|
-
version = "2.12.
|
|
7
|
+
version = "2.12.2"
|
|
8
8
|
description = "Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables."
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
license = { file = "LICENSE.txt" }
|
|
@@ -91,6 +91,7 @@ execsql = "execsql.cli:_legacy_main"
|
|
|
91
91
|
execsql-format = "execsql.format:main"
|
|
92
92
|
|
|
93
93
|
[project.urls]
|
|
94
|
+
Homepage = "https://execsql2.readthedocs.io"
|
|
94
95
|
Repository = "https://github.com/geocoug/execsql"
|
|
95
96
|
Issues = "https://github.com/geocoug/execsql/issues"
|
|
96
97
|
|
|
@@ -158,7 +159,7 @@ skip-magic-trailing-comma = false
|
|
|
158
159
|
line-ending = "auto"
|
|
159
160
|
|
|
160
161
|
[tool.bumpversion]
|
|
161
|
-
current_version = "2.12.
|
|
162
|
+
current_version = "2.12.2"
|
|
162
163
|
commit = true
|
|
163
164
|
commit_args = "--no-verify"
|
|
164
165
|
tag = true
|
|
@@ -132,6 +132,8 @@ class ErrInfo(ExecSqlError):
|
|
|
132
132
|
|
|
133
133
|
if self.type == "db":
|
|
134
134
|
self.error_message = "**** Error in SQL statement."
|
|
135
|
+
elif self.type == "assert":
|
|
136
|
+
self.error_message = "**** Assertion failed."
|
|
135
137
|
elif self.type == "cmd":
|
|
136
138
|
self.error_message = "**** Error in metacommand."
|
|
137
139
|
elif self.type == "log":
|
|
@@ -66,7 +66,7 @@ def x_assert(**kwargs: Any) -> None:
|
|
|
66
66
|
if _state.exec_log is not None:
|
|
67
67
|
_state.exec_log.log_user_msg(f"ASSERT passed: {condition}")
|
|
68
68
|
else:
|
|
69
|
-
raise ErrInfo(type="
|
|
69
|
+
raise ErrInfo(type="assert", other_msg=message)
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
def x_if(**kwargs: Any) -> None:
|
|
@@ -71,9 +71,22 @@ class TestXAssertRaisesOnFalseCondition:
|
|
|
71
71
|
with (
|
|
72
72
|
patch.object(_state, "xcmd_test", return_value=False),
|
|
73
73
|
patch.object(_state, "exec_log", mock_log),
|
|
74
|
-
pytest.raises(ErrInfo),
|
|
74
|
+
pytest.raises(ErrInfo) as exc_info,
|
|
75
75
|
):
|
|
76
76
|
x_assert(condtest="ROWCOUNT > 0", message=None, metacommandline="ASSERT ROWCOUNT > 0")
|
|
77
|
+
assert exc_info.value.type == "assert"
|
|
78
|
+
|
|
79
|
+
def test_false_condition_eval_err_says_assertion_failed(self) -> None:
|
|
80
|
+
mock_log = _make_exec_log()
|
|
81
|
+
with (
|
|
82
|
+
patch.object(_state, "xcmd_test", return_value=False),
|
|
83
|
+
patch.object(_state, "exec_log", mock_log),
|
|
84
|
+
pytest.raises(ErrInfo) as exc_info,
|
|
85
|
+
):
|
|
86
|
+
x_assert(condtest="ROWCOUNT > 0", message='"expected rows"', metacommandline="ASSERT ROWCOUNT > 0")
|
|
87
|
+
err_msg = exc_info.value.eval_err()
|
|
88
|
+
assert err_msg.startswith("**** Assertion failed.")
|
|
89
|
+
assert "expected rows" in err_msg
|
|
77
90
|
|
|
78
91
|
def test_false_condition_with_double_quoted_message(self) -> None:
|
|
79
92
|
mock_log = _make_exec_log()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|