API Reference¶
The pages in this section are auto-generated from the source docstrings and show the full Python interface of the execsql package.
If you want to extend execsql — add a new exporter format, support a new database, or add an importer for a file type — start with the Contributing guides, which give you step-by-step walkthroughs and copy-paste skeletons. The API pages here serve as the detailed reference those guides link to.
For programmatic use, see the Library API section below. For a high-level overview of how all the pieces fit together, start with the Architecture & Design Guide.
Library API¶
The primary public API is execsql.run():
from execsql import run, ScriptResult, ScriptError, ExecSqlError
result: ScriptResult = run(
script="pipeline.sql", # or sql="SELECT 1;"
dsn="sqlite:///my.db", # or connection=existing_db_object
variables={"KEY": "value"}, # optional substitution variables
halt_on_error=True, # stop on first error (default)
new_db=False, # create DB if missing
)
See the README for full examples.
Thread Safety¶
run() is thread-safe. Each call creates an isolated RuntimeContext stored in thread-local storage, so concurrent calls from different threads do not share database connections, substitution variables, or execution state.
import threading
from execsql import run
def etl_worker(script, dsn):
result = run(script=script, dsn=dsn)
print(f"{script}: {'OK' if result.success else 'FAIL'}")
threads = [
threading.Thread(target=etl_worker, args=("load_us.sql", "postgresql://host/us_db")),
threading.Thread(target=etl_worker, args=("load_eu.sql", "postgresql://host/eu_db")),
]
for t in threads:
t.start()
for t in threads:
t.join()
Each thread gets its own database connections, IF/LOOP stacks, substitution variables, and error state. No locking is required.
File Output¶
WRITE ... TO <file>, TEE, and the other metacommands that write text files hand their output to a separate FileWriter process. run() starts one if none is running, and flushes and closes every file before returning — so a file a script wrote is on disk and complete the moment run() hands back control.
from execsql import run
result = run(sql='-- !x! WRITE "done" to report.txt\nselect 1;\n', dsn="sqlite:///data.db")
open("report.txt").read() # "done\n" — readable immediately
The writer process stays up afterwards and is reused by later run() calls, exactly as the CLI keeps one for the life of the process; it is shut down automatically at interpreter exit. A writer you started yourself is left alone — run() neither replaces nor stops it.
Interactive interpreters
On macOS and Windows, Python starts subprocesses with the spawn method, which re-imports the calling program's __main__ module. That fails in a REPL, a notebook, or python -c, so the writer cannot start and file output is discarded — with a warning on stderr rather than in silence. Run scripts that write files from a .py file, guarded by if __name__ == "__main__":.
Extension Guides¶
| Extension type | Guide | API reference |
|---|---|---|
| New export format | Adding Exporters | Exporters |
| New database adapter | Adding Database Adapters | Databases |
| New import format | Adding Importers | Importers |
| New metacommand | Adding Metacommands | Metacommands |
Modules¶
| Module | Description |
|---|---|
| CLI | Entry-point functions and argument parsing |
| Databases | Database ABC and DatabasePool |
| Exporters | Export metadata, write specs, and format writers |
| Importers | Data-import back-end used by all importer sub-modules |
| Metacommands | Dispatch table and metacommand handler modules |