Skip to content

Commit

Permalink
Creating SQLAlchemy URL properly (#91)
Browse files Browse the repository at this point in the history
* Creating SQLAlchemy URL properly

* Shutting up mypy

* Making mypy happy
  • Loading branch information
ahsimb authored Mar 12, 2024
1 parent 9532180 commit c91ce68
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 17 deletions.
2 changes: 2 additions & 0 deletions doc/changes/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Changes

* [0.2.9](changes_0.2.9.md)
* [0.2.8](changes_0.2.8.md)
* [0.2.7](changes_0.2.7.md)
* [0.2.6](changes_0.2.6.md)
Expand All @@ -17,6 +18,7 @@
---
hidden:
---
changes_0.2.9
changes_0.2.8
changes_0.2.7
changes_0.2.6
Expand Down
11 changes: 11 additions & 0 deletions doc/changes/changes_0.2.9.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Exasol Notebook Connector 0.2.9, released T.B.D.

## Summary

Post-release fixes.

## Changes

* AI-Lab#230: Connection via SQLAlchemy fails
- Enables fingerprints in the host name.
- Handles correctly special characters in the password.
26 changes: 14 additions & 12 deletions exasol/nb_connector/connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import pyexasol # type: ignore
import sqlalchemy # type: ignore
from sqlalchemy.engine.url import URL # type: ignore

import exasol.bucketfs as bfs # type: ignore
from exasol.nb_connector.secret_store import Secrets
Expand Down Expand Up @@ -111,7 +112,7 @@ def open_pyexasol_connection(conf: Secrets, **kwargs) -> pyexasol.ExaConnection:
def open_sqlalchemy_connection(conf: Secrets):
"""
Creates an Exasol SQLAlchemy websocket engine using provided configuration parameters.
Does NOT set the default schema, even if it is defined in the configuration.
Sets the default schema if it is defined in the configuration.
The configuration should provide the following parameters:
- Server address and port (db_host_name, db_port),
Expand All @@ -125,21 +126,22 @@ def open_sqlalchemy_connection(conf: Secrets):
it is possible to set the client TLS/SSL certificate.
"""

websocket_url = (
f"exa+websocket://{conf.get(CKey.db_user)}:{conf.get(CKey.db_password)}@{get_external_host(conf)}"
)

delimiter = "?"
query_params = {}
encryption = _optional_encryption(conf)
if encryption is not None:
websocket_url = (
f'{websocket_url}{delimiter}ENCRYPTION={"Yes" if encryption else "No"}'
)
delimiter = "&"

query_params['ENCRYPTION'] = 'Yes' if encryption else 'No'
certificate_validation = _extract_ssl_options(conf).get("cert_reqs")
if (certificate_validation is not None) and (not certificate_validation):
websocket_url = f"{websocket_url}{delimiter}SSLCertificate=SSL_VERIFY_NONE"
query_params['SSLCertificate'] = 'SSL_VERIFY_NONE'

websocket_url = URL.create('exa+websocket',
username=conf.get(CKey.db_user),
password=conf.get(CKey.db_password),
host=conf.get(CKey.db_host_name),
port=int(getattr(conf, CKey.db_port.name)),
database=conf.get(CKey.db_schema),
query=query_params
)

return sqlalchemy.create_engine(websocket_url)

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "exasol-notebook-connector"
version = "0.2.8"
version = "0.2.9"
description = "Components, tools, APIs, and configurations in order to connect Jupyter notebooks to Exasol and various other systems."
packages = [ {include = "exasol"}, ]
authors = [ "Christoph Kuhnke <[email protected]>" ]
Expand Down
9 changes: 6 additions & 3 deletions test/unit/test_connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from contextlib import ExitStack
from typing import Optional
from unittest.mock import create_autospec
from sqlalchemy.engine import make_url

import pytest

Expand Down Expand Up @@ -106,21 +107,23 @@ def test_open_pyexasol_connection_error(mock_connect, conf):

@unittest.mock.patch("sqlalchemy.create_engine")
def test_open_sqlalchemy_connection(mock_create_engine, conf):
setattr(conf, CKey.db_port.name, conf.get(CKey.db_port))
open_sqlalchemy_connection(conf)
mock_create_engine.assert_called_once_with(
f"exa+websocket://{conf.get(CKey.db_user)}:{conf.get(CKey.db_password)}@{get_external_host(conf)}"
make_url(f"exa+websocket://{conf.get(CKey.db_user)}:{conf.get(CKey.db_password)}@{get_external_host(conf)}")
)


@unittest.mock.patch("sqlalchemy.create_engine")
def test_open_sqlalchemy_connection_ssl(mock_create_engine, conf):
conf.save(CKey.db_encryption, "True")
conf.save(CKey.cert_vld, "False")
setattr(conf, CKey.db_port.name, conf.get(CKey.db_port))

open_sqlalchemy_connection(conf)
mock_create_engine.assert_called_once_with(
f"exa+websocket://{conf.get(CKey.db_user)}:{conf.get(CKey.db_password)}@{get_external_host(conf)}"
"?ENCRYPTION=Yes&SSLCertificate=SSL_VERIFY_NONE"
make_url(f"exa+websocket://{conf.get(CKey.db_user)}:{conf.get(CKey.db_password)}@{get_external_host(conf)}"
"?ENCRYPTION=Yes&SSLCertificate=SSL_VERIFY_NONE")
)


Expand Down
2 changes: 1 addition & 1 deletion version.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
# If you need to change the version, do so in the project.toml, e.g. by using `poetry version X.Y.Z`.
MAJOR = 0
MINOR = 2
PATCH = 8
PATCH = 9
VERSION = f"{MAJOR}.{MINOR}.{PATCH}"

0 comments on commit c91ce68

Please sign in to comment.