execsql2 2.5.0__tar.gz → 2.7.1__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.5.0 → execsql2-2.7.1}/.claude/project_context.md +107 -19
- {execsql2-2.5.0 → execsql2-2.7.1}/.gitignore +1 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/CHANGELOG.md +39 -1
- {execsql2-2.5.0 → execsql2-2.7.1}/CLAUDE.md +1 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/PKG-INFO +5 -2
- {execsql2-2.5.0 → execsql2-2.7.1}/README.md +1 -1
- execsql2-2.7.1/docs/about/divergence.md +171 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/reference/metacommands.md +16 -1
- {execsql2-2.5.0 → execsql2-2.7.1}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +1 -1
- {execsql2-2.5.0 → execsql2-2.7.1}/pyproject.toml +8 -4
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/__init__.py +3 -3
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/delimited.py +2 -2
- execsql2-2.7.1/src/execsql/exporters/markdown.py +126 -0
- execsql2-2.7.1/src/execsql/exporters/xlsx.py +317 -0
- execsql2-2.7.1/src/execsql/exporters/yaml.py +87 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/gui/tui.py +132 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/__init__.py +203 -182
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/dispatch.py +11 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/io.py +2 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/io_export.py +75 -0
- execsql2-2.7.1/src/execsql/state.py +452 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/conftest.py +2 -0
- execsql2-2.7.1/tests/db/test_base.py +1953 -0
- execsql2-2.7.1/tests/exporters/test_delimited.py +1242 -0
- execsql2-2.7.1/tests/exporters/test_markdown.py +302 -0
- execsql2-2.7.1/tests/exporters/test_xlsx.py +419 -0
- execsql2-2.7.1/tests/exporters/test_yaml.py +140 -0
- execsql2-2.7.1/tests/metacommands/test_connect.py +1807 -0
- execsql2-2.7.1/tests/test_engine.py +1305 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_state.py +109 -3
- {execsql2-2.5.0 → execsql2-2.7.1}/uv.lock +5 -1
- {execsql2-2.5.0 → execsql2-2.7.1}/zensical.toml +1 -0
- execsql2-2.5.0/src/execsql/state.py +0 -391
- execsql2-2.5.0/tests/db/test_base.py +0 -423
- execsql2-2.5.0/tests/exporters/test_delimited.py +0 -460
- {execsql2-2.5.0 → execsql2-2.7.1}/.claude/agents/dba.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.claude/agents/herald.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.claude/agents/inspector.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.claude/agents/oracle.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.claude/agents/patcher.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.claude/agents/qa.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.claude/agents/scribe.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.claude/commands/code-oracle.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.claude/commands/migrate.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.claude/commands/review-changes.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.claude/commands/test-module.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.claude/commands/update-changelog.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.claude/commands/where-is.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.claude/state/status.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.github/workflows/ci-cd.yml +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.pre-commit-config.yaml +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.python-version +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/.readthedocs.yaml +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/CONTRIBUTING.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/LICENSE.txt +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/NOTICE +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/SECURITY.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/about/contributors.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/about/copyright.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/api/cli.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/api/db.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/api/exporters.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/api/importers.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/api/index.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/api/metacommands.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/dev/architecture.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/getting-started/installation.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/getting-started/syntax.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/guides/debugging.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/guides/documentation.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/guides/encoding.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/guides/examples.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/guides/formatter.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/guides/logging.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/guides/usage.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/actions.png +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/actions2.png +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/checkboxes.png +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/connect.b64 +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/connect.png +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/create_conf.png +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/entry_form.png +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/execsql_console.png +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/fatals.png +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/logo_small.png +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/unmatched.png +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/index.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/reference/configuration.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/reference/security.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/docs/reference/substitution_vars.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/justfile +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/__init__.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/__main__.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/cli/help.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/cli/run.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/config.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/constants.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/access.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/base.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/dsn.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/duckdb.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/factory.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/firebird.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/mysql.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/oracle.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/postgres.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/sqlite.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/sqlserver.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exceptions.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/duckdb.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/ods.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/templates.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/xls.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/format.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/gui/base.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/gui/console.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/importers/base.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/control.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/debug.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/io_fileops.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/models.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/parser.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/py.typed +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/script/control.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/script/engine.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/script/variables.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/types.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/errors.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/fileio.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/templates/README.md +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/templates/config_settings.sqlite +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/templates/execsql.conf +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/templates/make_config_db.sql +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/templates/md_compare.sql +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/templates/md_glossary.sql +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/templates/md_upsert.sql +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/templates/pg_compare.sql +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/templates/pg_glossary.sql +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/templates/pg_upsert.sql +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/templates/script_template.sql +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/templates/ss_compare.sql +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/templates/ss_glossary.sql +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/templates/ss_upsert.sql +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/__init__.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/cli/__init__.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/cli/test_cli.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/db/__init__.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/db/test_duckdb.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/db/test_factory.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/db/test_postgres.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/__init__.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_base.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_db.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_json.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/gui/__init__.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/gui/test_backends.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/importers/__init__.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/integration/__init__.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/integration/conftest.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/integration/test_postgres.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_config.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_config_data.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_constants.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_exceptions.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_format.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_mail.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_models.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_package.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_parser.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_registry.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_script.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_types.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/__init__.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_auth.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_errors.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_regex.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_strings.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_timer.py +0 -0
- {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_timer_extra.py +0 -0
|
@@ -189,10 +189,10 @@ Triggered on: push to `main`, any tag `v*.*.*`, pull requests.
|
|
|
189
189
|
|
|
190
190
|
## Versioning
|
|
191
191
|
|
|
192
|
-
`bump-my-version` manages versions. Current: `2.
|
|
192
|
+
`bump-my-version` manages versions. Current: `2.5.0`. Bump commands:
|
|
193
193
|
|
|
194
|
-
- `just bump-patch` → 2.
|
|
195
|
-
- `just bump-minor` → 2.
|
|
194
|
+
- `just bump-patch` → 2.5.0 → 2.5.1
|
|
195
|
+
- `just bump-minor` → 2.5.0 → 2.6.0
|
|
196
196
|
Bumps commit + tag. Pre-commit hook runs `uv lock` + stages `uv.lock`.
|
|
197
197
|
|
|
198
198
|
## Ruff Config
|
|
@@ -243,44 +243,132 @@ the foreseeable future.
|
|
|
243
243
|
| Docs reorganized (getting-started/reference/guides/about) | 2.4.6 | 2026-03 |
|
|
244
244
|
| Pre-commit hook for `execsql-format` | 2.4.3 | 2026-03 |
|
|
245
245
|
| VS Code syntax highlighting extension | 2.4.x | 2026-03 |
|
|
246
|
+
| Docstring coverage 81% on public API | 2.5.0 | 2026-04 |
|
|
247
|
+
| Developer architecture guide with Mermaid diagrams | 2.5.0 | 2026-04 |
|
|
248
|
+
| Cursor context managers (exec_cmd + vacuum) | 2.5.0 | 2026-04 |
|
|
249
|
+
| Exporter Protocol types (QueryExporter, RowsetExporter) | 2.5.0 | 2026-04 |
|
|
250
|
+
| SubVarSet.merge() optimization (O(1) vs O(V)) | 2.5.0 | 2026-04 |
|
|
251
|
+
| GitHub Actions upgraded to Node.js 24 | 2.5.0 | 2026-04 |
|
|
252
|
+
| GitHub issue/PR templates + SECURITY.md | 2.5.0 | 2026-04 |
|
|
253
|
+
| Database ABC already in place (verified) | 2.5.0 | 2026-04 |
|
|
254
|
+
| Dispatch optimization already in place (verified) | 2.5.0 | 2026-04 |
|
|
255
|
+
| PostgreSQL integration tests (9 tests, CI Docker) | 2.5.0 | 2026-04 |
|
|
256
|
+
| MySQL integration tests (9 tests, CI Docker) | 2.5.0 | 2026-04 |
|
|
246
257
|
|
|
247
258
|
______________________________________________________________________
|
|
248
259
|
|
|
249
|
-
### v2.
|
|
260
|
+
### v2.6 — Architecture & Internal Quality
|
|
250
261
|
|
|
251
|
-
|
|
262
|
+
- [x] **`state.py` → `RuntimeContext` refactor** — 33 mutable globals consolidated into a slotted `RuntimeContext` class with transparent module proxy. `get_context()`/`set_context()` API added. Zero external call-site changes.
|
|
263
|
+
- [x] **`noqa` cleanup in `metacommands/__init__.py`** — removed all 180 redundant `# noqa` comments; `__all__` already satisfies ruff F401.
|
|
264
|
+
- [x] **Coverage push to 86%** — 403 new tests (3010 total) covering `db/base.py` (55→99%), `metacommands/connect.py` (36→100%), `script/engine.py` (77→95%), `exporters/delimited.py` (76→95%). Remaining gap to 90% is in GUI, ODS, and import handlers.
|
|
252
265
|
|
|
253
|
-
|
|
266
|
+
### v2.7 — New Export/Import Formats
|
|
254
267
|
|
|
255
|
-
|
|
268
|
+
- [x] **Parquet import** — already existed as `IMPORT TO table FROM PARQUET file` (verified present).
|
|
269
|
+
- [x] **YAML export** — `FORMAT YAML` via PyYAML, list-of-dicts with native type preservation.
|
|
270
|
+
- [x] **Markdown (GFM) export** — `FORMAT MARKDOWN` / `MD`, pipe tables with alignment and escaping.
|
|
271
|
+
- [x] **Excel (XLSX) multi-sheet export** — `FORMAT XLSX` single + multi-sheet via openpyxl, bold headers, inventory sheet, sheet name deduplication.
|
|
256
272
|
|
|
257
|
-
### v2.
|
|
273
|
+
### v2.8 — Scripting Power Features
|
|
258
274
|
|
|
259
|
-
|
|
275
|
+
- [ ] **`ASSERT` metacommand** — `-- !x! ASSERT <condition> "message"`. Data validation for CI pipelines and sanity checks.
|
|
276
|
+
- [ ] **`--dry-run` improvements** — show SQL with substitution variables expanded, not just raw metacommands.
|
|
277
|
+
- [ ] **Script profiling (`--profile`)** — per-statement execution times, summary report at end. Leverages existing `Timer` infrastructure.
|
|
278
|
+
- [ ] **Parallel execution blocks** — `PARALLEL BEGIN ... PARALLEL END` for independent statements. See design notes below.
|
|
260
279
|
|
|
261
|
-
|
|
262
|
-
- **Cursor context managers** — explicit cursor lifecycle in `execute()`, `select_data()`, `select_rowsource()`
|
|
263
|
-
- **Metacommand dispatch optimization** — consider dict/trie lookup instead of O(N) regex scan
|
|
264
|
-
- **Variable substitution optimization** — reduce O(V×D) complexity
|
|
265
|
-
- **Exporter protocol** — define a `Protocol` or ABC for exporters with a common interface
|
|
280
|
+
### v2.9 — Library API & Developer Experience
|
|
266
281
|
|
|
267
|
-
|
|
282
|
+
- [ ] **Programmatic Python API** — `execsql.run(script, db=...)` for notebook/pipeline usage. Depends on `RuntimeContext` refactor.
|
|
283
|
+
- [ ] **TOML configuration** — `execsql.toml` as modern alternative to legacy INI format (coexist initially).
|
|
284
|
+
|
|
285
|
+
### v2.10 — Testing & CI Hardening
|
|
286
|
+
|
|
287
|
+
- [ ] **Property-based testing (Hypothesis)** — for parsers, type inference, substitution variables.
|
|
288
|
+
- [ ] **Parser fuzzing** — `CondParser` and `NumericParser` handle arbitrary user input; fuzz for edge cases.
|
|
289
|
+
- [ ] **Nightly CI against latest DB driver versions** — catch upstream breakage in psycopg2, pymysql, duckdb, etc.
|
|
290
|
+
- [ ] **CI benchmarks** — track substitution variable and dispatch performance over time.
|
|
291
|
+
|
|
292
|
+
### v2.11 — Documentation & Community
|
|
293
|
+
|
|
294
|
+
- [ ] **Cookbook / recipes page** — real-world examples: ETL workflows, HTML reports, data validation pipelines.
|
|
295
|
+
- [ ] **Migration guide from upstream execsql** — what changed, what's new, how to switch.
|
|
296
|
+
- [ ] **Interactive tutorial** — guided walkthrough script against a bundled SQLite DB.
|
|
268
297
|
|
|
269
298
|
### v3.0+ — Future
|
|
270
299
|
|
|
271
|
-
- **Plugin system** —
|
|
300
|
+
- [ ] **Plugin system** — entry points for `execsql.exporters`, `execsql.importers`, `execsql.metacommands` allowing external packages to register new handlers.
|
|
301
|
+
- [ ] **LSP / language server** — for the VS Code extension: autocomplete metacommands, validate substitution variables, jump-to-definition for `INCLUDE`d scripts.
|
|
272
302
|
|
|
273
303
|
______________________________________________________________________
|
|
274
304
|
|
|
275
305
|
### Ongoing / No-milestone
|
|
276
306
|
|
|
277
|
-
- PostgreSQL integration tests (requires external server — CI docker service)
|
|
278
|
-
- MySQL integration tests (same)
|
|
279
|
-
- `savedscripts` memory pruning
|
|
280
307
|
- Textual TUI polish
|
|
281
308
|
|
|
282
309
|
______________________________________________________________________
|
|
283
310
|
|
|
311
|
+
### Design Notes: Parallel Execution Blocks
|
|
312
|
+
|
|
313
|
+
**Concept:** Allow users to declare groups of independent SQL statements that
|
|
314
|
+
can run concurrently, reducing wall-clock time for ETL scripts with
|
|
315
|
+
independent work.
|
|
316
|
+
|
|
317
|
+
**Syntax:**
|
|
318
|
+
```sql
|
|
319
|
+
-- !x! PARALLEL BEGIN [WORKERS=4]
|
|
320
|
+
INSERT INTO summary_a SELECT ... FROM raw_data;
|
|
321
|
+
INSERT INTO summary_b SELECT ... FROM raw_data;
|
|
322
|
+
INSERT INTO summary_c SELECT ... FROM raw_data;
|
|
323
|
+
-- !x! PARALLEL END
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**How it would work in the engine:**
|
|
327
|
+
|
|
328
|
+
1. When `runscripts()` encounters `PARALLEL BEGIN`, the engine enters a
|
|
329
|
+
"collecting" mode (similar to how `LOOP` compiles commands into a
|
|
330
|
+
`CommandList` before executing). Statements are accumulated but not run.
|
|
331
|
+
|
|
332
|
+
2. On `PARALLEL END`, the collected statements are dispatched to a
|
|
333
|
+
`concurrent.futures.ThreadPoolExecutor` (or `ProcessPoolExecutor`).
|
|
334
|
+
Each statement gets its own database cursor (or connection from
|
|
335
|
+
`DatabasePool`).
|
|
336
|
+
|
|
337
|
+
3. The main `runscripts()` loop blocks at the `PARALLEL END` until all
|
|
338
|
+
futures complete. Errors from any worker are collected and raised as
|
|
339
|
+
a combined `ErrInfo`.
|
|
340
|
+
|
|
341
|
+
**Key constraints and design decisions:**
|
|
342
|
+
|
|
343
|
+
- **No shared mutable state inside parallel blocks.** Substitution variable
|
|
344
|
+
writes (`SET`), `IF/ELSE`, `LOOP`, `INCLUDE`, and other control-flow
|
|
345
|
+
metacommands are **prohibited** inside `PARALLEL` blocks — only raw SQL
|
|
346
|
+
and simple export metacommands are allowed. The parser rejects anything
|
|
347
|
+
else at compile time.
|
|
348
|
+
|
|
349
|
+
- **Connection handling.** Each parallel worker needs its own cursor or
|
|
350
|
+
connection. `DatabasePool` already exists in `db/base.py` but currently
|
|
351
|
+
manages one connection per named alias. This would need a pool-per-alias
|
|
352
|
+
model (e.g., min/max connections) or each worker opens a fresh connection
|
|
353
|
+
from the same DSN.
|
|
354
|
+
|
|
355
|
+
- **Depends on `RuntimeContext` refactor.** The current `state.py` globals
|
|
356
|
+
(especially `commandliststack`, `if_stack`, `subvars`) are not
|
|
357
|
+
thread-safe. Workers would need isolated read-only snapshots of
|
|
358
|
+
substitution variables and their own cursor state. The `RuntimeContext`
|
|
359
|
+
work in v2.6 is a prerequisite.
|
|
360
|
+
|
|
361
|
+
- **`WORKERS=N`** defaults to `min(len(statements), os.cpu_count())`.
|
|
362
|
+
Configurable via the metacommand or `execsql.toml`.
|
|
363
|
+
|
|
364
|
+
- **Transaction semantics.** Each statement runs in its own implicit
|
|
365
|
+
transaction (autocommit). If the user needs atomicity across the whole
|
|
366
|
+
block, they wrap it in `BEGIN BATCH ... END BATCH` outside the parallel
|
|
367
|
+
block — but that negates parallelism for most backends, so this is
|
|
368
|
+
mainly useful for independent ETL loads.
|
|
369
|
+
|
|
370
|
+
______________________________________________________________________
|
|
371
|
+
|
|
284
372
|
## Open Design Questions
|
|
285
373
|
|
|
286
374
|
### Distribution / single-file invocation model
|
|
@@ -13,7 +13,45 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
-
## [2.
|
|
16
|
+
## [2.7.1] - 2026-04-01
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Fix `AttributeError: module 'execsql.state' has no attribute 'dedup_words'` when importing CSV files with `DEDUP_COL_HDRS` enabled — `dedup_words` is now correctly imported from `execsql.utils.strings` instead of accessed through the state module.
|
|
21
|
+
|
|
22
|
+
______________________________________________________________________
|
|
23
|
+
|
|
24
|
+
## [2.7.0] - 2026-04-01
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **Markdown export** (`FORMAT MARKDOWN` / `FORMAT MD`) — GitHub-flavored pipe tables with column alignment, pipe/backslash escaping, and zip support. No dependencies required.
|
|
29
|
+
- **YAML export** (`FORMAT YAML`) — list-of-dicts output via PyYAML with native type preservation (int, float, null). Requires `PyYAML` (included in `formats` extras).
|
|
30
|
+
- **XLSX export** (`FORMAT XLSX`) — single-sheet and multi-sheet Excel export via openpyxl with bold headers, native type preservation, sheet name deduplication, and a "Datasheets" inventory sheet. Multi-sheet syntax: `EXPORT table1, table2 TO file.xlsx AS XLSX`.
|
|
31
|
+
|
|
32
|
+
______________________________________________________________________
|
|
33
|
+
|
|
34
|
+
## [2.6.0] - 2026-04-01
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- Textual TUI `console_save()` — writes console output to a file, matching Tkinter parity.
|
|
39
|
+
- Keyboard shortcut hints on Textual TUI dialog screens — Escape to cancel, Enter to submit, with `Footer` widget on all major dialog screens.
|
|
40
|
+
- `RuntimeContext` class in `state.py` — groups all 33 mutable runtime globals into a single slotted object. Enables isolated contexts for testing and future concurrent execution.
|
|
41
|
+
- `get_context()` / `set_context()` public API for programmatic access to the active runtime context.
|
|
42
|
+
- Divergence from Upstream documentation page (`docs/about/divergence.md`) listing all user-visible changes since the fork.
|
|
43
|
+
- Test coverage raised from 80% to 86% — 403 new tests across `db/base.py`, `metacommands/connect.py`, `script/engine.py`, and `exporters/delimited.py`.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- `state.py` module now uses a `types.ModuleType` subclass that transparently proxies attribute reads and writes to the active `RuntimeContext` instance. All existing `_state.foo` call sites continue working with zero changes.
|
|
48
|
+
- `reset()` simplified from 40 lines with 7 `global` statements to a clean context replacement (preserving `filewriter`).
|
|
49
|
+
- `initialize()` and `endloop()` rewritten to use `_ctx` directly instead of `global` statements.
|
|
50
|
+
- Removed 180 redundant `# noqa` suppressions from `metacommands/__init__.py` — the existing `__all__` already satisfies ruff F401.
|
|
51
|
+
|
|
52
|
+
______________________________________________________________________
|
|
53
|
+
|
|
54
|
+
## [2.5.0] - 2026-04-01
|
|
17
55
|
|
|
18
56
|
### Added
|
|
19
57
|
|
|
@@ -51,4 +51,5 @@ A multi-agent system where specialized agents collaborate to improve, extend, de
|
|
|
51
51
|
- All code must pass `ruff check` and target Python 3.10+
|
|
52
52
|
- **Every user-visible change must be reflected in `CHANGELOG.md`** under the `[Unreleased]` section using [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) categories: Added, Changed, Fixed, Removed. Do not leave changelog updates for later — include them in the same commit or PR as the code change.
|
|
53
53
|
- **Every user-visible change must also update documentation** — review and revise `README.md`, `docs/`, and any other relevant documentation to stay consistent with the code. New CLI options, features, or behavior changes must be reflected in the README Options table, feature list, and the corresponding docs page. Do not leave doc updates for later. Docs are organized into subdirectories: `docs/getting-started/`, `docs/reference/` (configuration, metacommands, substitution_vars, security), `docs/guides/`, `docs/about/`, `docs/api/`, `docs/dev/`.
|
|
54
|
+
- **Every change that diverges from upstream execsql v1.130.1 must update `docs/about/divergence.md`** — this includes new features, changed behavior, security fixes, and removed functionality. The divergence page is the canonical record of how execsql2 differs from the original monolith. Keep entries concise and organized under the existing section headings (Added Features, Changed Behavior, Security and Correctness Fixes, Removed Features).
|
|
54
55
|
- **After every version bump and push, monitor CI** — push with `git push && git push --tags` to include version tags, then run `gh run list --limit 1` to get the run ID, then `gh run watch <id> --exit-status` to block until it completes. Bump commits trigger PyPI publish and GitHub Release, so failures must be caught and fixed immediately.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.7.1
|
|
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
5
|
Project-URL: Repository, https://github.com/geocoug/execsql
|
|
6
6
|
Project-URL: Issues, https://github.com/geocoug/execsql/issues
|
|
@@ -54,6 +54,7 @@ Requires-Dist: polars; extra == 'all'
|
|
|
54
54
|
Requires-Dist: psycopg2-binary; extra == 'all'
|
|
55
55
|
Requires-Dist: pymysql; extra == 'all'
|
|
56
56
|
Requires-Dist: pyodbc; extra == 'all'
|
|
57
|
+
Requires-Dist: pyyaml; extra == 'all'
|
|
57
58
|
Requires-Dist: tables; extra == 'all'
|
|
58
59
|
Requires-Dist: xlrd; extra == 'all'
|
|
59
60
|
Provides-Extra: all-db
|
|
@@ -76,6 +77,7 @@ Requires-Dist: openpyxl; extra == 'dev'
|
|
|
76
77
|
Requires-Dist: polars; extra == 'dev'
|
|
77
78
|
Requires-Dist: pre-commit>=3.5.0; extra == 'dev'
|
|
78
79
|
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
80
|
+
Requires-Dist: pyyaml; extra == 'dev'
|
|
79
81
|
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
80
82
|
Requires-Dist: tables; extra == 'dev'
|
|
81
83
|
Requires-Dist: tox-uv>=1.13.1; extra == 'dev'
|
|
@@ -91,6 +93,7 @@ Requires-Dist: jinja2; extra == 'formats'
|
|
|
91
93
|
Requires-Dist: odfpy; extra == 'formats'
|
|
92
94
|
Requires-Dist: openpyxl; extra == 'formats'
|
|
93
95
|
Requires-Dist: polars; extra == 'formats'
|
|
96
|
+
Requires-Dist: pyyaml; extra == 'formats'
|
|
94
97
|
Requires-Dist: tables; extra == 'formats'
|
|
95
98
|
Requires-Dist: xlrd; extra == 'formats'
|
|
96
99
|
Provides-Extra: mssql
|
|
@@ -227,7 +230,7 @@ Run `execsql --help` for the full option list, or `execsql -m` to list all metac
|
|
|
227
230
|
# Features
|
|
228
231
|
|
|
229
232
|
- Import data from CSV, TSV, Excel, OpenDocument, Feather, or Parquet files into a database table.
|
|
230
|
-
- Export query results in
|
|
233
|
+
- Export query results in 20+ formats including CSV, TSV, JSON, YAML, XML, HTML, Markdown, LaTeX, XLSX, OpenDocument, Feather, Parquet, HDF5, DuckDB, SQLite, plain text, and Jinja2 templates.
|
|
231
234
|
- Copy data between databases, including across different DBMS types.
|
|
232
235
|
- Conditionally execute SQL and metacommands using `IF`/`ELSE`/`ENDIF` based on data values, DBMS type, or user input.
|
|
233
236
|
- Loop over blocks of SQL and metacommands using `LOOP`/`ENDLOOP`.
|
|
@@ -120,7 +120,7 @@ Run `execsql --help` for the full option list, or `execsql -m` to list all metac
|
|
|
120
120
|
# Features
|
|
121
121
|
|
|
122
122
|
- Import data from CSV, TSV, Excel, OpenDocument, Feather, or Parquet files into a database table.
|
|
123
|
-
- Export query results in
|
|
123
|
+
- Export query results in 20+ formats including CSV, TSV, JSON, YAML, XML, HTML, Markdown, LaTeX, XLSX, OpenDocument, Feather, Parquet, HDF5, DuckDB, SQLite, plain text, and Jinja2 templates.
|
|
124
124
|
- Copy data between databases, including across different DBMS types.
|
|
125
125
|
- Conditionally execute SQL and metacommands using `IF`/`ELSE`/`ENDIF` based on data values, DBMS type, or user input.
|
|
126
126
|
- Loop over blocks of SQL and metacommands using `LOOP`/`ENDLOOP`.
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Divergence from Upstream
|
|
2
|
+
|
|
3
|
+
execsql2 is a maintained fork of [execsql](https://execsql.readthedocs.io/)
|
|
4
|
+
v1.130.1 by R. Dreas Nielsen. This page documents all user-visible changes
|
|
5
|
+
since the fork was created: new features, changed behavior, security fixes,
|
|
6
|
+
and removed functionality.
|
|
7
|
+
|
|
8
|
+
For a chronological view, see the [Change Log](change_log.md).
|
|
9
|
+
|
|
10
|
+
______________________________________________________________________
|
|
11
|
+
|
|
12
|
+
## Added Features
|
|
13
|
+
|
|
14
|
+
### CLI Options
|
|
15
|
+
|
|
16
|
+
| Flag | Description |
|
|
17
|
+
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
18
|
+
| `--version` | Print version and exit (Rich-formatted). |
|
|
19
|
+
| `-c` / `--command` | Execute an inline SQL or metacommand string instead of a script file. |
|
|
20
|
+
| `--dsn` / `--connection-string` | Accept a standard database URL (e.g. `postgresql://user:pass@host/db`). Supports `postgresql`, `mysql`, `mssql`, `oracle`, `firebird`, `sqlite`, and `duckdb` schemes. |
|
|
21
|
+
| `--output-dir` | Set a default base directory for export output files. |
|
|
22
|
+
| `--progress` | Show a Rich progress bar during long-running IMPORT operations. |
|
|
23
|
+
| `--dump-keywords` | Emit all metacommand keywords, conditionals, config options, and export formats as structured JSON. |
|
|
24
|
+
| `--gui-framework` | Select GUI backend: `tkinter` (default) or `textual` (terminal UI). |
|
|
25
|
+
| `--dry-run` | Parse the script and print the full command list without connecting to a database or executing anything. |
|
|
26
|
+
|
|
27
|
+
### Export Formats
|
|
28
|
+
|
|
29
|
+
| Format | Description |
|
|
30
|
+
| ----------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
31
|
+
| `PARQUET` | Export query or table results to Apache Parquet via `polars`. |
|
|
32
|
+
| `FEATHER` | Export to Apache Feather/IPC via `polars` + `pyarrow` (upstream used `pandas`). |
|
|
33
|
+
| `YAML` | Export query or table results as a YAML sequence of mappings via `PyYAML`. |
|
|
34
|
+
| `MARKDOWN` / `MD` | Export query or table results as a GitHub-Flavored Markdown (GFM) pipe table. Pure Python, no optional dependencies. |
|
|
35
|
+
| `XLSX` | Export query or table results to an Excel XLSX workbook via `openpyxl` (single or multi-sheet). |
|
|
36
|
+
|
|
37
|
+
### Metacommands
|
|
38
|
+
|
|
39
|
+
| Metacommand | Description |
|
|
40
|
+
| ---------------------- | --------------------------------------------------------------------- |
|
|
41
|
+
| `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
|
|
42
|
+
| `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
|
|
43
|
+
|
|
44
|
+
### Configuration Options
|
|
45
|
+
|
|
46
|
+
New options in `execsql.conf`:
|
|
47
|
+
|
|
48
|
+
| Option | Section | Description |
|
|
49
|
+
| -------------------------- | ----------- | ----------------------------------------------------------------- |
|
|
50
|
+
| `use_keyring` | `[connect]` | Use the OS keyring for credential storage (default: `yes`). |
|
|
51
|
+
| `show_progress` | `[input]` | Enable Rich progress bar for IMPORT (default: `no`). |
|
|
52
|
+
| `import_progress_interval` | `[input]` | Log a status line every N rows during IMPORT (default: `0`). |
|
|
53
|
+
| `log_sql` | `[config]` | Enable SQL audit logging (default: `no`). |
|
|
54
|
+
| `max_log_size_mb` | `[config]` | Rotate the log file at this size in MB (default: `0` = disabled). |
|
|
55
|
+
|
|
56
|
+
### Tools
|
|
57
|
+
|
|
58
|
+
| Tool | Description |
|
|
59
|
+
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
60
|
+
| `execsql-format` | Standalone CLI for normalizing metacommand indentation and uppercasing SQL keywords. Supports `--check` and `--in-place` modes. Also available as a [pre-commit hook](../guides/formatter.md). |
|
|
61
|
+
|
|
62
|
+
### GUI
|
|
63
|
+
|
|
64
|
+
| Feature | Description |
|
|
65
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
66
|
+
| Textual TUI backend | Full terminal-UI backend via the `textual` library. Provides all dialog types (password, pause, message, entry, compare, action, etc.) in the terminal. |
|
|
67
|
+
| Console fallback | Text-only backend that handles GUI calls in headless environments by printing to stdout. |
|
|
68
|
+
|
|
69
|
+
### Authentication
|
|
70
|
+
|
|
71
|
+
| Feature | Description |
|
|
72
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
73
|
+
| OS keyring integration | When the `keyring` package is installed, passwords are stored in and retrieved from the OS credential store (macOS Keychain, Windows Credential Manager, Linux SecretService). |
|
|
74
|
+
| Keyring retry on auth failure | If a stored password is rejected, the stale entry is deleted, the user is re-prompted, and the new password is saved automatically. |
|
|
75
|
+
|
|
76
|
+
### Logging Enhancements
|
|
77
|
+
|
|
78
|
+
| Feature | Description |
|
|
79
|
+
| ----------------------------- | ---------------------------------------------------------------------------------- |
|
|
80
|
+
| Per-event ISO 8601 timestamps | `status`, `connect`, `action`, and `user_msg` log entries include a timestamp. |
|
|
81
|
+
| Run duration in exit record | The `exit` log record includes elapsed wall-clock time. |
|
|
82
|
+
| Run ID millisecond precision | Run identifier format changed from `%Y%m%d_%H%M_%S` to `%Y%m%d_%H%M_%S_NNN`. |
|
|
83
|
+
| SQL audit log record | New `sql` record type containing DB name, line number, and query text. |
|
|
84
|
+
| Import progress log | Periodic row-count status lines during IMPORT when `import_progress_interval > 0`. |
|
|
85
|
+
|
|
86
|
+
### Developer / Packaging
|
|
87
|
+
|
|
88
|
+
| Feature | Description |
|
|
89
|
+
| --------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
90
|
+
| VS Code syntax highlighting | Auto-generated `tmLanguage.json` grammar from the dispatch table. |
|
|
91
|
+
| `py.typed` marker | PEP 561 marker enabling downstream static type checking. |
|
|
92
|
+
| Structured keyword registry | `--dump-keywords` introspects the dispatch table and outputs JSON used by the grammar generator and test suite. |
|
|
93
|
+
|
|
94
|
+
______________________________________________________________________
|
|
95
|
+
|
|
96
|
+
## Changed Behavior
|
|
97
|
+
|
|
98
|
+
### CLI Interface
|
|
99
|
+
|
|
100
|
+
The CLI framework changed from `optparse` to [Typer](https://typer.tiangolo.com/) with Rich-formatted help text. All original short flags (`-a` through `-z`) are preserved. The tool can be invoked as either `execsql` or `execsql2`.
|
|
101
|
+
|
|
102
|
+
### Internal State Management
|
|
103
|
+
|
|
104
|
+
All 33 mutable runtime globals in `state.py` have been consolidated into a `RuntimeContext` object. The module uses a transparent proxy so existing code is unaffected, but the architecture now supports isolated contexts for testing and future concurrent execution.
|
|
105
|
+
|
|
106
|
+
### Substitution Variables
|
|
107
|
+
|
|
108
|
+
- **Cycle detection** — `substitute_vars()` raises an error after 100 iterations to prevent infinite loops when variables reference each other cyclically. Upstream had no protection.
|
|
109
|
+
- **O(1) substitution** — Variable substitution uses a single combined regex and dict lookup instead of O(V) per-variable regex passes. Behavior is identical; performance is improved.
|
|
110
|
+
|
|
111
|
+
### Database Adapters
|
|
112
|
+
|
|
113
|
+
- **`Database` is an ABC** — `open_db()` and `exec_cmd()` are abstract methods. Subclasses that omit them raise `TypeError` at instantiation instead of at call time.
|
|
114
|
+
- **Connection timeouts** — PostgreSQL and SQLite adapters accept a connection timeout parameter (default 30 seconds).
|
|
115
|
+
- **DuckDB temporal types** — `TIMESTAMPTZ`, `TIMESTAMP`, `DATE`, `TIME` now map to native DuckDB types instead of `TEXT`.
|
|
116
|
+
|
|
117
|
+
### Error Handling
|
|
118
|
+
|
|
119
|
+
- **Exception hierarchy** — All custom exceptions inherit from `ExecSqlError`, enabling `except ExecSqlError` to catch any execsql-originated error.
|
|
120
|
+
- **Exception chaining** — All `raise` statements inside `except` blocks preserve the original traceback via `from`.
|
|
121
|
+
|
|
122
|
+
______________________________________________________________________
|
|
123
|
+
|
|
124
|
+
## Security and Correctness Fixes
|
|
125
|
+
|
|
126
|
+
These are behavioral changes driven by security or correctness issues in the upstream code.
|
|
127
|
+
|
|
128
|
+
### Injection Fixes
|
|
129
|
+
|
|
130
|
+
| Area | Fix |
|
|
131
|
+
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
132
|
+
| Database metadata queries | `schema_exists()`, `table_exists()`, `column_exists()`, `table_columns()`, `view_exists()`, `role_exists()` across all 9 adapters now use parameterized queries. Upstream used string interpolation. |
|
|
133
|
+
| `import_entire_file()` | Column names are quoted with `quote_identifier()` instead of interpolated into INSERT statements. |
|
|
134
|
+
| PostgreSQL `CREATE DATABASE` | Database name and encoding are quoted. COPY delimiter and quote character are validated. |
|
|
135
|
+
| `$SHEETS_TABLES_VALUES` | Sheet names from ODS/XLS imports are escaped before embedding in SQL. |
|
|
136
|
+
| HTTP `Content-Disposition` | Filename is sanitized to prevent HTTP response splitting in SERVE. |
|
|
137
|
+
|
|
138
|
+
### Template and Export Safety
|
|
139
|
+
|
|
140
|
+
| Area | Fix |
|
|
141
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------ |
|
|
142
|
+
| Jinja2 sandboxing | Templates run in `SandboxedEnvironment` instead of the default `jinja2.Template`. |
|
|
143
|
+
| HTML export | Column headers and cell values are escaped with `html.escape()` to prevent XSS. |
|
|
144
|
+
| XML export | Values are escaped with `xml.sax.saxutils.escape()`. Invalid XML element name characters are replaced. |
|
|
145
|
+
| JSON export | The `description` field uses `json.dumps()` instead of string interpolation. |
|
|
146
|
+
|
|
147
|
+
### Credential and Logging Safety
|
|
148
|
+
|
|
149
|
+
| Area | Fix |
|
|
150
|
+
| ---------------------------- | ------------------------------------------------------------------------------------------ |
|
|
151
|
+
| ODBC password redaction | Connection strings in log output have `Pwd=***` substituted before logging. |
|
|
152
|
+
| `enc_password` documentation | Prominent warnings that XOR encryption is obfuscation only — keys are hardcoded in source. |
|
|
153
|
+
|
|
154
|
+
### Bug Fixes
|
|
155
|
+
|
|
156
|
+
| Area | Fix |
|
|
157
|
+
| --------------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
158
|
+
| Oracle default port | Corrected from `5432` (PostgreSQL) to `1521`. |
|
|
159
|
+
| MySQL `LOAD DATA INFILE` encoding | Python encoding names are now mapped to MySQL charset names. |
|
|
160
|
+
| `dt_cast` type converters | Base `Database` class auto-populates 8 type converters that were previously left empty after the refactor. |
|
|
161
|
+
| `FileWriter` CPU busy-loop | Uses blocking `queue.get(timeout=0.1)` instead of `get_nowait()` in a tight loop. |
|
|
162
|
+
| Substitution variable cycles | 100-iteration limit prevents infinite loops on cyclic variable references. |
|
|
163
|
+
|
|
164
|
+
______________________________________________________________________
|
|
165
|
+
|
|
166
|
+
## Removed Features
|
|
167
|
+
|
|
168
|
+
| Feature | Reason |
|
|
169
|
+
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
170
|
+
| Airspeed template processor | The `airspeed` library (Velocity clone) is unmaintained since ~2018. Use `FORMAT jinja` instead. The `airspeed` value for `template_processor` in `execsql.conf` is no longer accepted. |
|
|
171
|
+
| Python 2 compatibility | All Python 2 constructs (`stringtypes`, `u""` literals, `optparse`, etc.) have been removed. execsql2 requires Python 3.10+. |
|
|
@@ -932,7 +932,22 @@ JSON_TS or JSON_TABLESCHEMA
|
|
|
932
932
|
|
|
933
933
|
LATEX
|
|
934
934
|
|
|
935
|
-
: Input for the [
|
|
935
|
+
: Input for the [LaTeX](https://www.latex-project.org/) typesetting system. If the "APPEND" keyword is not used, a complete document (of class article) will be written. If the "APPEND" keyword is used, only the table definition will be written to the output file. If the "APPEND" keyword is used and an existing output file contains an \\end directive, the table will be written before that directive rather than at the physical end of the file. Wide or long tables may exceed LaTeΧ's default page size. If the "DESCRIPTION" keyword is used, the given description will be used as the table's caption. Data exported in LaTeX format cannot be written into a zipfile.
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
MARKDOWN or MD
|
|
939
|
+
|
|
940
|
+
: [GitHub-Flavored Markdown](https://github.github.com/gfm/) pipe table. Column values are aligned and pipe (`|`) and backslash (`\`) characters in data are escaped. If the "DESCRIPTION" keyword is used, the description is written as an HTML comment (`<!-- ... -->`) before the table. If the "APPEND" keyword is used, only the table is appended (no repeated headers). No optional dependencies required.
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
XLSX
|
|
944
|
+
|
|
945
|
+
: [Excel](https://www.microsoft.com/en-us/microsoft-365/excel) workbook in the Office Open XML format. One or more tables (or views) can be exported to an XLSX workbook. Each table will be exported to a separate worksheet within the workbook, with the first row containing bold column headers. To export multiple tables, their names must be separated by commas. The "APPEND" keyword can be used to add worksheets to an existing workbook. The name of the view or table exported will be used as the worksheet name; if this conflicts with a sheet already in the workbook, a number will be appended to make the sheet name unique. A "Datasheets" inventory sheet is created with author, date, description, and source information for each data sheet. Data types are preserved natively (integers, floats, dates, datetimes, booleans). The `openpyxl` library must be installed (`pip install execsql2[excel]`). Data exported in XLSX format cannot be written into a zipfile.
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
YAML
|
|
949
|
+
|
|
950
|
+
: [YAML](https://yaml.org/) sequence of mappings. Each row is represented as a mapping (dictionary) with column names as keys. Python data types are preserved — integers remain integers, floats remain floats, and `None` becomes YAML `null`. If the "APPEND" keyword is used, a new YAML document is appended to the file (multi-document stream). The `PyYAML` library must be installed (`pip install execsql2[formats]`). No description text is included in the output even if provided.
|
|
936
951
|
|
|
937
952
|
|
|
938
953
|
SQLITE
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
},
|
|
119
119
|
"export-formats": {
|
|
120
120
|
"comment": "Export/import format names",
|
|
121
|
-
"match": "(?i)\\b(json_tableschema|cgi-html|feather|json_ts|txt-and|unitsep|duckdb|sqlite|values|latex|plain|hdf5|html|json|tabq|tsvq|b64|csv|ods|raw|tab|tsv|txt|xml|us)\\b",
|
|
121
|
+
"match": "(?i)\\b(json_tableschema|cgi-html|markdown|feather|json_ts|txt-and|unitsep|duckdb|sqlite|values|latex|plain|hdf5|html|json|tabq|tsvq|xlsx|yaml|b64|csv|ods|raw|tab|tsv|txt|xml|md|us)\\b",
|
|
122
122
|
"name": "support.constant.execsql"
|
|
123
123
|
},
|
|
124
124
|
"secondary-operators": {
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "execsql2"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.7.1"
|
|
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" }
|
|
@@ -56,7 +56,7 @@ firebird = ["firebird-driver"]
|
|
|
56
56
|
oracle = ["oracledb"]
|
|
57
57
|
odbc = ["pyodbc"]
|
|
58
58
|
# Feature bundles
|
|
59
|
-
formats = ["odfpy", "xlrd", "openpyxl", "Jinja2", "polars", "tables"]
|
|
59
|
+
formats = ["odfpy", "xlrd", "openpyxl", "Jinja2", "polars", "tables", "PyYAML"]
|
|
60
60
|
auth = ["keyring"]
|
|
61
61
|
# Convenience groups
|
|
62
62
|
all-db = [
|
|
@@ -147,6 +147,10 @@ ignore = [
|
|
|
147
147
|
"UP017", # Use datetime.UTC alias (not available in Python 3.10)
|
|
148
148
|
]
|
|
149
149
|
|
|
150
|
+
[tool.ruff.lint.per-file-ignores]
|
|
151
|
+
# F822: context attrs are proxied via __getattr__/__setattr__ on the module class, not defined as module-level variables
|
|
152
|
+
"src/execsql/state.py" = ["F822"]
|
|
153
|
+
|
|
150
154
|
[tool.ruff.format]
|
|
151
155
|
quote-style = "double"
|
|
152
156
|
indent-style = "space"
|
|
@@ -154,7 +158,7 @@ skip-magic-trailing-comma = false
|
|
|
154
158
|
line-ending = "auto"
|
|
155
159
|
|
|
156
160
|
[tool.bumpversion]
|
|
157
|
-
current_version = "2.
|
|
161
|
+
current_version = "2.7.1"
|
|
158
162
|
commit = true
|
|
159
163
|
commit_args = "--no-verify"
|
|
160
164
|
tag = true
|
|
@@ -178,7 +182,7 @@ addopts = [
|
|
|
178
182
|
"--cov=execsql",
|
|
179
183
|
"--cov-branch",
|
|
180
184
|
"--cov-report=xml",
|
|
181
|
-
"--cov-fail-under=
|
|
185
|
+
"--cov-fail-under=85",
|
|
182
186
|
"--strict-markers",
|
|
183
187
|
"--strict-config",
|
|
184
188
|
"--color=yes",
|
|
@@ -9,9 +9,9 @@ handlers can access them via ``_state.write_query_to_csv`` etc. without
|
|
|
9
9
|
importing directly from here.
|
|
10
10
|
|
|
11
11
|
Sub-modules: ``base``, ``delimited``, ``json``, ``xml``, ``html``,
|
|
12
|
-
``latex``, ``ods``, ``xls``, ``zip``, ``raw``, ``pretty``,
|
|
13
|
-
``templates``, ``feather``, ``parquet``, ``duckdb``,
|
|
14
|
-
``protocol``.
|
|
12
|
+
``latex``, ``markdown``, ``ods``, ``xls``, ``zip``, ``raw``, ``pretty``,
|
|
13
|
+
``values``, ``templates``, ``feather``, ``parquet``, ``duckdb``,
|
|
14
|
+
``sqlite``, ``protocol``.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
from execsql.exporters.protocol import QueryExporter, RowsetExporter
|
|
@@ -27,7 +27,7 @@ from execsql.exceptions import ErrInfo
|
|
|
27
27
|
from execsql.models import DataTable
|
|
28
28
|
from execsql.utils.errors import exception_desc
|
|
29
29
|
from execsql.utils.fileio import filewriter_close
|
|
30
|
-
from execsql.utils.strings import clean_words, fold_words
|
|
30
|
+
from execsql.utils.strings import clean_words, dedup_words, fold_words
|
|
31
31
|
|
|
32
32
|
__all__ = ["LineDelimiter", "CsvFile", "CsvWriter", "DelimitedWriter", "write_delimited_file"]
|
|
33
33
|
|
|
@@ -677,7 +677,7 @@ class CsvFile(EncodedFile):
|
|
|
677
677
|
if conf.fold_col_hdrs != "no":
|
|
678
678
|
colnames = fold_words(colnames, conf.fold_col_hdrs)
|
|
679
679
|
if conf.dedup_col_hdrs:
|
|
680
|
-
colnames =
|
|
680
|
+
colnames = dedup_words(colnames)
|
|
681
681
|
return colnames
|
|
682
682
|
|
|
683
683
|
def column_headers(self) -> list[str]:
|