From 290248285a08b8f1f5f58f958a5d4bd740d1d846 Mon Sep 17 00:00:00 2001 From: Nicola Coretti Date: Fri, 26 Jan 2024 08:34:48 +0100 Subject: [PATCH] Make odbc based dialects optional (#437) --------- Co-authored-by: Torsten Kilias --- .github/workflows/ci-cd.yml | 4 +- .github/workflows/ci.yaml | 4 +- CHANGELOG.rst | 5 + README.rst | 162 +++++++++--------------- doc/conf.py | 5 +- doc/developer_guide/developer_guide.rst | 1 + doc/index.rst | 20 +-- poetry.lock | 11 +- pyproject.toml | 7 +- 9 files changed, 89 insertions(+), 130 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 57e3b4a4..1d310ee8 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -84,8 +84,8 @@ jobs: with: python-version: ${{ matrix.python }} - - name: Install python project dependencies including trubodbc - run: poetry install --extras "turbodbc" + - name: Install python project dependencies including turbodbc + run: poetry install --all-extras if: ${{ matrix.connector == 'turbodbc' }} - name: Run Test for Python ${{ matrix.python }} using ${{ matrix.connector }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6e24bbdc..f8ee84b1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -83,8 +83,8 @@ jobs: with: python-version: ${{ matrix.python }} - - name: Install python project dependencies including trubodbc - run: poetry install --extras "turbodbc" + - name: Install python project dependencies including turbodbc + run: poetry install --all-extras if: ${{ matrix.connector == 'turbodbc' }} - name: Run Test for Python ${{ matrix.python }} using ${{ matrix.connector }} diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 454570ab..deb854d2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,11 @@ Unreleased ========== +🔧 Changed +----------- +- Made websockets the default way to use sqlalchemy with exasol +- Made pydobc an optional dependency + 🧰 Internal ----------- - Relocked dependencies diff --git a/README.rst b/README.rst index cd206b07..2c9dbfd2 100644 --- a/README.rst +++ b/README.rst @@ -43,60 +43,55 @@ SQLAlchemy Dialect for EXASOL DB :alt: PyPI - Downloads -How to get started ------------------- - -Currently, sqlalchemy-exasol supports multiple dialects. The core difference -being if the dialect is :code:`odbc` or :code:`websocket` based. - -Generally, we advise to use the websocket based Dialect, because odbc -based dialects require a good understanding of (unix)ODBC and the setup is -significant more complicated. +Getting Started with SQLAlchemy-Exasol +-------------------------------------- +SQLAlchemy-Exasol supports multiple dialects, primarily differentiated by whether they are ODBC or Websocket based. +Choosing a Dialect +++++++++++++++++++ -Turbodbc support -```````````````` +We recommend using the Websocket-based dialect due to its simplicity. ODBC-based dialects demand a thorough understanding of (Unix)ODBC, and the setup is considerably more complex. .. warning:: - Maintenance of this feature is on hold. Also it is very likely that turbodbc support will be dropped in future versions. + The maintenance of Turbodbc support is currently paused, and it may be phased out in future versions. + We are also planning to phase out the pyodbc support in the future. -- You can use Turbodbc with sqlalchemy_exasol if you use a python version >= 3.8. -- Multi row update is not supported, see - `test/test_update.py `_ for an example -Meet the system requirements -```````````````````````````` +System Requirements +------------------- - Python - An Exasol DB (e.g. `docker-db `_ or a `cloud instance `_) -ODBC-based dialects additionally require the following to be available and set up: +.. note:: -- The packages unixODBC and unixODBC-dev >= 2.2.14 -- The Exasol `ODBC driver `_ -- The ODBC.ini and ODBCINST.ini configurations files setup + For ODBC-Based Dialects, additional libraries required for ODBC are necessary + (for further details, checkout the `developer guide`_). +Setting Up Your Python Project +------------------------------ -Setup your python project and install sqlalchemy-exasol -``````````````````````````````````````````````````````` +Install SQLAlchemy-Exasol: .. code-block:: shell $ pip install sqlalchemy-exasol -for turbodbc support: +.. note:: -.. code-block:: shell + To use an ODBC-based dialect, you must specify it as an extra during installation. - $ pip install sqlalchemy-exasol[turbodbc] + .. code-block:: shell -Talk to the EXASOL DB using SQLAlchemy -`````````````````````````````````````` + pip install "sqlalchemy-exasol[pydobc]" + pip install "sqlalchemy-exasol[turbodbc]" -**Websocket based Dialect:** -For more details regarding the websocket support checkout the section: "What is Websocket support?" +Using SQLAlchemy with EXASOL DB +------------------------------- + +**Websocket based Dialect:** .. code-block:: python @@ -105,6 +100,31 @@ For more details regarding the websocket support checkout the section: "What is e = create_engine(url) r = e.execute("select 42 from dual").fetchall() +Examples: + +.. code-block:: python + + from sqlalchemy import create_engine + + engine = create_engine("exa+websocket://sys:exasol@127.0.0.1:8888") + with engine.connect() as con: + ... + +.. code-block:: python + + from sqlalchemy import create_engine + + # ATTENTION: + # In terms of security it is NEVER a good idea to turn of certificate validation!! + # In rare cases it may be handy for non-security related reasons. + # That said, if you are not a 100% sure about your scenario, stick with the + # secure defaults. + # In most cases, having a valid certificate and/or configuring the truststore(s) + # appropriately is the best/correct solution. + engine = create_engine("exa+websocket://sys:exasol@127.0.0.1:8888?SSLCertificate=SSL_VERIFY_NONE") + with engine.connect() as con: + ... + **Pyodbc (ODBC based Dialect):** @@ -125,24 +145,13 @@ For more details regarding the websocket support checkout the section: "What is r = e.execute("select 42 from dual").fetchall() -The dialect supports two types of connection urls creating an engine. A DSN (Data Source Name) mode and a host mode: - -.. list-table:: - - * - Type - - Example - * - DSN URL - - 'exa+pyodbc://USER:PWD@exa_test' - * - HOST URL - - 'exa+pyodbc://USER:PWD@192.168.14.227..228:1234/my_schema?parameter' - Features -++++++++ +-------- - SELECT, INSERT, UPDATE, DELETE statements -Notes -+++++ +General Notes +------------- - Schema name and parameters are optional for the host url - At least on Linux/Unix systems it has proven valuable to pass 'CONNECTIONLCALL=en_US.UTF-8' as a url parameter. This will make sure that the client process (Python) and the EXASOL driver (UTF-8 internal) know how to interpret code pages correctly. @@ -154,66 +163,13 @@ Notes .. _test_drive: https://www.exasol.com/test-it-now/cloud/ .. _test_docker_image: https://github.com/exasol/docker-db +Known Issues +------------ +* Insert + - Insert multiple empty rows via prepared statements does not work in all cases + Development & Testing -````````````````````` +--------------------- See `developer guide`_ -What is Websocket support? -`````````````````````````` -In the context of SQLA and Exasol, Websocket support means that an SQLA dialect -supporting the `Exasol Websocket Protocol `_ -is provided. - -Using the websocket based protocol instead over ODBC will provide various advantages: - -* Less System Dependencies -* Easier to use than ODBC based driver(s) -* Lock free metadata calls etc. - -For further details `Why a Websockets API `_. - -Example Usage(s) -++++++++++++++++++ - -.. code-block:: python - - from sqla import create_engine - - engine = create_engine("exa+websocket://sys:exasol@127.0.0.1:8888") - with engine.connect() as con: - ... - -.. code-block:: python - - from sqla import create_engine - # ATTENTION: - # In terms of security it is NEVER a good idea to turn of certificate validation!! - # In rare cases it may be handy for non-security related reasons. - # That said, if you are not a 100% sure about your scenario, stick with the - # secure defaults. - # In most cases, having a valid certificate and/or configuring the truststore(s) - # appropriately is the best/correct solution. - engine = create_engine("exa+websocket://sys:exasol@127.0.0.1:8888?SSLCertificate=SSL_VERIFY_NONE") - with engine.connect() as con: - ... - -Supported Connection Parameters -+++++++++++++++++++++++++++++++ -.. list-table:: - - * - Parameter - - Values - - Comment - * - ENCRYPTION - - Y, Yes, N, No - - Y or Yes Enable Encryption (TLS) default, N or No disable Encryption - * - SSLCertificate - - SSL_VERIFY_NONE - - Disable certificate validation - - -Known Issues -++++++++++++ -* Insert - - Insert multiple empty rows via prepared statements does not work in all cases diff --git a/doc/conf.py b/doc/conf.py index cfd67caf..b85c68ee 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -28,13 +28,12 @@ # The full version, including alpha/beta/rc tags release = VERSION - # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ["sphinx_copybutton"] +extensions = ["sphinx_copybutton", "sphinx.ext.intersphinx"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -44,7 +43,6 @@ # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] - # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for @@ -57,7 +55,6 @@ "dark_logo": "dark-exasol-logo.svg", } - # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". diff --git a/doc/developer_guide/developer_guide.rst b/doc/developer_guide/developer_guide.rst index 1006916a..d2b7a3a3 100644 --- a/doc/developer_guide/developer_guide.rst +++ b/doc/developer_guide/developer_guide.rst @@ -15,6 +15,7 @@ Tools * integration-test-docker-environment_ * Prerequisites_ + Libraries +++++++++ * unixodbc diff --git a/doc/index.rst b/doc/index.rst index 97317310..70e4df42 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,26 +1,20 @@ SQLAlchemy-Exasol ================= -SQLAlchemy-Exasol is an SQLAlchemy dialect extension. - -Overview --------- -The dialect is the system SQLAlchemy uses to communicate with various types of DBAPI implementations and databases. -The sections that follow contain reference documentation and notes specific to the usage of each backend, -as well as notes for the various DBAPIs. - -For more details have a look into the `SQLAlchemy documentation `_. +SQLAlchemy-Exasol is a dialect extension for SQLAlchemy, implementing support for Exasol databases. Getting Started --------------- -#. `Install the Exasol-ODBC driver `_ - -#. Add `sqlalchemy-exasol `_ as a dependency +#. Install the `SQLAlchemy-Exasol extension `_ .. code-block:: shell $ pip install sqlalchemy-exasol + .. note:: + + SQLAlchemy will be installed due to the fact that it is an required dependency for SQLAlchemy-Exasol. + #. Execute queries .. code-block:: python @@ -38,5 +32,5 @@ For more details on SQLAlchemy consult it's `documenation =3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] name = "pyodbc" version = "5.0.1" description = "DB API module for ODBC" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "pyodbc-5.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9824b175db875a2dd116c7cf16dc3bdf14855404417afd145c5b839da222cb46"}, @@ -2657,9 +2657,10 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.link testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [extras] +pyodbc = ["pyodbc"] turbodbc = ["turbodbc"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4.0" -content-hash = "9122b5f9a4168064885d2d274a11c83a8334678617f263c9b204558735ee181b" +content-hash = "596367a5b774d5b97d8990ea84a329dd21f7d29908d69076abfedc50ddc1dd11" diff --git a/pyproject.toml b/pyproject.toml index a56018f9..340d8d71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,6 @@ exclude = [] [tool.poetry.dependencies] python = ">=3.8,<4.0" -pyodbc = ">=4.0.34,<6" packaging = ">=21.3" pyexasol = "^0.25.1" sqlalchemy = ">=1.4,<2" @@ -70,6 +69,10 @@ sqlalchemy = ">=1.4,<2" version = "==4.5.4" optional = true +[tool.poetry.dependencies.pyodbc] +version = ">=4.0.34,<6" +optional = true + [tool.poetry.group.dev.dependencies] nox = ">=2022.1.7" urlscan = ">=0.9.9" @@ -90,8 +93,10 @@ pyupgrade = ">=3.0.0" rich = "^13.3.1" exasol-integration-test-docker-environment = "^1.5.0" pytest-history = ">=0.2.0" +pyodbc = ">=4.0.34,<6" [tool.poetry.extras] +pyodbc = ["pyodbc"] turbodbc = ["turbodbc"] [tool.poetry.plugins."sqlalchemy.dialects"]