Blog / 2025-10-06
Using the SyneHQ SQL Extension from Jupyter
Harsh Vardhan Goswami
On this page
Notebooks make it easy to mix SQL, Python, charts, and notes in one place. They can also make it easy to copy a database URL, password, or API token into a cell and later commit it to a repository. That pattern is convenient at first and difficult to unwind once a notebook is shared, exported, or scheduled.
The SyneHQ SQL extension is intended to move database connection details out of notebook cells. The notebook identifies a configured connection and sends a query through the SQL service. The extension still needs a way to authenticate the caller to that service. It does not make access control disappear, and it should not be described as a way to run production queries without an identity.
This article describes the extension as implemented in this repository, where it fits in a notebook workflow, and the checks an operator should make before relying on it for sensitive data.
The notebook credential problem
A direct database connection often puts a connection string in one of three places: a notebook cell, a local environment file, or a shared notebook image. Each choice has tradeoffs. A literal password can be copied into notebook output or source control. An environment variable keeps the value out of a cell, but it is still a secret distributed to every runtime that needs it. A shared image can make rotation and access changes slow to apply.
The extension changes the object stored in the notebook from database credentials to a connection identifier. A caller sends its SyneHQ API key to the SQL service, and the service resolves the named connection. In the extension code, the client retrieves connection configuration and sends execution requests over HTTP. That is a useful separation, but it is not a claim that the service, its network path, or every destination is safe by default.
The practical goal is narrower: keep destination database credentials out of notebook text and let the service decide whether an authenticated caller may use a configured connection. Platform API keys remain sensitive. Treat them as user credentials, not as harmless configuration values.
Connection model and access boundary
The %%sql cell magic accepts a connection identifier, such as analytics. The extension uses its configured SQL service URL and sends an X-Api-Key header when an API key is available. The service client can request connection details and execute a query using that named connection.
This creates two separate decisions:
- Can the caller authenticate to the SQL service?
- Can that caller use the requested connection and execute the requested statement?
The extension participates in the first decision by passing an API key. The second decision belongs to the service and the database configuration behind it. Teams should verify those server-side checks for their deployment rather than infer role-based access, audit records, statement blocking, or database permissions from the notebook interface.
The extension includes optional client settings for validation, input sanitization, logging, caching, and write operations. Those are configuration and implementation controls, not a substitute for database permissions or a review of the service policy. In particular, a client-side check cannot be the only control protecting a writable production database.
A small query workflow
Install the package in the same Python environment as Jupyter, then load the extension in a notebook.
pip install syne-sql-extension
%load_ext syne_sql_extension
Provide the platform API key through the runtime environment or another secret-injection mechanism. The placeholder below is intentionally not a real credential.
export SQL_SERVICE_API_KEY='<managed-platform-api-key>'
Run a bounded read query against a connection already configured for the service:
%%sql analytics --limit 100
SELECT
created_at,
status,
COUNT(*) AS order_count
FROM orders
WHERE created_at >= DATE '2025-01-01'
GROUP BY created_at, status
ORDER BY created_at DESC
The default result is a Pandas DataFrame. The extension also exposes HTML and JSON output modes. Choose an output format based on the next step: DataFrames for Python analysis, HTML for an interactive notebook display, and JSON when a cell needs a structured value. Output formatting does not change the authorization decision or the amount of data the query returns.
Parameters need review too
The magic supports Python interpolation, including a typed list form. It is useful for notebook exploration, but it changes the query text before it reaches the service. Keep the interpolation expression simple, check the rendered intent, and do not treat it as a replacement for server-side parameter binding where the destination driver supports it.
region_codes = ["east", "west"]
%%sql analytics --limit 100
SELECT region, SUM(amount) AS revenue
FROM invoices
WHERE region IN {region_codes:list}
GROUP BY region
ORDER BY revenue DESC
The extension also supports assigning a query result to a Python name using <<:
%%sql analytics --limit 100
recent_orders <<
SELECT order_id, created_at, total
FROM orders
ORDER BY created_at DESC
Use explicit limits while exploring. A DataFrame is easy to pass to plotting code or write to a local file, so a successful query can still move data outside the controls that protected the original connection. Notebook users need guidance on exports, shared kernels, and retained cell output.
Failure modes worth planning for
The extension config exposes request timeouts, retry counts, connection caching, and query settings. Their actual effect depends on the service and destination database. A retry can repeat a request; a cache can return data that is older than the underlying table; a timeout may leave work running at the database. These are operational questions to test with the database owner.
There are also ordinary notebook concerns. A kernel can retain values longer than intended. Notebook checkpoints and rendered outputs can preserve query results. A shared JupyterHub or a scheduled job needs its own identity and secret-delivery plan. Do not put an API key in a notebook global, command line argument, or saved configuration file if that file can be read by other users.
For write-capable connections, start with a separate non-production connection and a test dataset. Decide whether the service should allow non-SELECT statements, what timeout is acceptable, who can change connection definitions, and how a user can report an unexpected result. Those policies reduce exposure only when the deployment enforces them.
Getting started checklist
- Install the extension in the kernel environment and load it with
%load_ext syne_sql_extension. - Obtain a platform API key through the approved secret-delivery path; do not paste it into the notebook.
- Confirm the SQL service URL and the connection identifier with the service operator.
- Start with a limited read query against a non-production or approved read-only connection.
- Verify the service-side authorization decision for the caller and connection.
- Review the configured timeout, retry, cache, logging, and write-operation settings for the deployment.
- Decide how notebook output, exported DataFrames, and scheduled kernels are retained and accessed.
