# Connection management with ``DuckCon`` `DuckCon` centralises DuckDB connection handling so resource management, extension loading, and configuration changes are explicit. This guide walks through the core features that power application backends and highlights patterns the DuckPlus team uses in services, notebooks, and CLI utilities. ## Lifecycle management `DuckCon` can be used either as a context manager or by calling :meth:`~duckplus.duckcon.DuckCon.connect`/:meth:`~duckplus.duckcon.DuckCon.close` explicitly. In both cases the class maintains a single live connection at a time, guarding against concurrent reuse. The ``is_open`` property and explicit error messages make it trivial to write defensive wrappers. ```python from duckplus import DuckCon manager = DuckCon(database="warehouse.duckdb", read_only=True) with manager as con: assert con.sql("SELECT 1").fetchone() == (1,) assert manager.connection is None # closed automatically try: with manager: pass with manager: pass except RuntimeError: print("Double entry prevented") ``` Attempting to enter the context manager twice raises a `RuntimeError`, keeping connection ownership clear. For CLI tools, wrap the ``with`` block in ``click.Context`` or similar frameworks so teardown happens even when commands raise exceptions. ## Extension loading Many DuckDB features live behind installable extensions. DuckPlus accepts the `extra_extensions` argument to install and load them eagerly. This is idempotent—extensions that are already installed are simply loaded. ```python manager = DuckCon(extra_extensions=("excel",)) with manager: extensions = {info.name: info for info in manager.extensions()} excel_available = extensions.get("excel") print(excel_available.loaded) ``` To add an extension after the connection is open, call ``manager.connection.install_extension(...)`` directly and then re-run your reader. DuckPlus will surface actionable errors when a download is not possible (for example, in offline environments), allowing callers to provide fallback paths. When you need to introspect the current state, use :meth:`DuckCon.extensions ` to retrieve a tuple of :class:`~duckplus.duckcon.ExtensionInfo` records with version, alias, and installation metadata: ```python with manager: for info in manager.extensions(): print(info.name, info.installed, info.loaded) ``` Because extension installation happens inside the ``with`` block, the metadata always reflects the current connection. ## Connection configuration The optional ``config`` dictionary lets you set DuckDB configuration parameters before the connection opens. DuckPlus normalises keyword arguments and defers to DuckDB's validation logic so the API stays future-proof. Use it to toggle settings such as ``default_null_order`` or `threads` when running analytical workloads. ```python manager = DuckCon(config={"allow_unsigned_extensions": True}) with manager as con: con.execute("PRAGMA verify_parallelism") ``` ## Relationship with relations Instances of :class:`~duckplus.relation.Relation` always reference the `DuckCon` that produced them. Helpers such as :meth:`duckplus.relation.Relation.append_parquet` validate that the originating connection is still open before issuing writes, preventing confusing runtime errors. Because the relation wrapper stores the manager reference, you can share connections across helpers without dealing with raw DuckDB handles. See the [immutable relation helper guide](relations.md) for deeper coverage of the immutable relation APIs. ## Registering custom helpers Helpers bind directly onto the :class:`DuckCon` class so every instance exposes the same fluent surface. Use :meth:`DuckCon.register_helper ` to attach a callable that receives the active ``DuckDBPyConnection``; the method wraps it in a bound method for you. DuckPlus ships file readers (``read_csv``, ``read_parquet``, ``read_json``) and extension-backed connectors (``read_odbc_query``, ``read_odbc_table``, ``read_excel``) that decorate themselves with :func:`duckplus.io.duckcon_helper` at import time. Call the helpers as methods on the manager or via :meth:`DuckCon.apply_helper ` without importing :mod:`duckplus.io`. Pass ``overwrite=True`` to replace these defaults with a project-specific implementation. Direct binding keeps helper discovery IDE-friendly while still offering an escape hatch for environment-specific functionality such as registering a ``parquet_scan`` macro or enabling custom pragmas. ```python def _apply_pragmas(connection, pragmas): for key, value in pragmas.items(): connection.execute(f"PRAGMA {key}={value}") manager.register_helper("set_pragmas", _apply_pragmas) with manager: manager.apply_helper("set_pragmas", {"threads": 4, "memory_limit": "4GB"}) ``` Because helpers close over :meth:`DuckCon.connection`, calling :meth:`DuckCon.apply_helper ` before the connection opens will raise a clear ``RuntimeError`` pointing back to ``DuckCon.connection``. ## Testing patterns The immutable design makes it straightforward to test connection-heavy code. In pytest fixtures, construct a ``DuckCon`` once and yield the object rather than the raw connection—relations created during the test will retain access to the fixture and keep assertions honest. When you need to simulate offline behaviour, set ``extra_extensions=()`` and monkeypatch ``connection.install_extension`` to raise, then assert that your application emits the fallback messaging you expect.