Skip to content

Commit

Permalink
Split dialect from driver, part 3: Swap in SQLAlchemy dialect and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
amotl committed Dec 20, 2023
1 parent 8644d48 commit 644a12f
Show file tree
Hide file tree
Showing 37 changed files with 128 additions and 303 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ library [crate-python].
The package is available from [PyPI] at [sqlalchemy-cratedb].
To install the most recent version, run:
```shell
pip install --upgrade cratedb-sqlalchemy
pip install --upgrade sqlalchemy-cratedb
```

## Documentation and help
Expand Down
9 changes: 9 additions & 0 deletions docs/backlog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Backlog

## Iteration +1
From dialect split-off.
```
tests.addTest(ParametrizedTestCase.parametrize(SqlAlchemyCompilerTest, param={"server_version_info": None}))
tests.addTest(ParametrizedTestCase.parametrize(SqlAlchemyCompilerTest, param={"server_version_info": (4, 0, 12)}))
tests.addTest(ParametrizedTestCase.parametrize(SqlAlchemyCompilerTest, param={"server_version_info": (4, 1, 10)}))
```
50 changes: 0 additions & 50 deletions src/crate/client/sqlalchemy/__init__.py

This file was deleted.

Empty file.
185 changes: 3 additions & 182 deletions src/crate/client/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,47 +21,19 @@

from __future__ import absolute_import

import json
import os
import socket
import sys
import unittest
import doctest
from pprint import pprint
from http.server import HTTPServer, BaseHTTPRequestHandler
import ssl
import time
import threading
import logging

import stopit

from crate.testing.layer import CrateLayer
from crate.testing.settings import \
crate_host, crate_path, crate_port, \
crate_transport_port, docs_path, localhost
from crate.testing.settings import crate_host, docs_path
from crate.client import connect
from .sqlalchemy import SA_VERSION, SA_1_4

from .test_cursor import CursorTest
from .test_connection import ConnectionTest
from .test_http import (
HttpClientTest,
ThreadSafeHttpClientTest,
KeepAliveClientTest,
ParamsTest,
RetryOnTimeoutServerTest,
RequestsCaBundleTest,
TestUsernameSentAsHeader,
TestCrateJsonEncoder,
TestDefaultSchemaHeader,
)
from .sqlalchemy.tests import test_suite_unit as sqlalchemy_test_suite_unit
from .sqlalchemy.tests import test_suite_integration as sqlalchemy_test_suite_integration
from sqlalchemy_cratedb import SA_VERSION, SA_1_4

makeSuite = unittest.TestLoader().loadTestsFromTestCase

log = logging.getLogger('crate.testing.layer')
log = logging.getLogger()
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
log.addHandler(ch)
Expand All @@ -73,46 +45,9 @@ def cprint(s):
print(s)


settings = {
'udc.enabled': 'false',
'lang.js.enabled': 'true',
'auth.host_based.enabled': 'true',
'auth.host_based.config.0.user': 'crate',
'auth.host_based.config.0.method': 'trust',
'auth.host_based.config.98.user': 'trusted_me',
'auth.host_based.config.98.method': 'trust',
'auth.host_based.config.99.user': 'me',
'auth.host_based.config.99.method': 'password',
}
crate_layer = None


def ensure_cratedb_layer():
"""
In order to skip individual tests by manually disabling them within
`def test_suite()`, it is crucial make the test layer not run on each
and every occasion. So, things like this will be possible::
./bin/test -vvvv --ignore_dir=testing
TODO: Through a subsequent patch, the possibility to individually
unselect specific tests might be added to `def test_suite()`
on behalf of environment variables.
A blueprint for this kind of logic can be found at
https://github.com/crate/crate/commit/414cd833.
"""
global crate_layer

if crate_layer is None:
crate_layer = CrateLayer('crate',
crate_home=crate_path(),
port=crate_port,
host=localhost,
transport_port=crate_transport_port,
settings=settings)
return crate_layer


def setUpCrateLayerBaseline(test):
test.globs['crate_host'] = crate_host
test.globs['pprint'] = pprint
Expand Down Expand Up @@ -202,115 +137,6 @@ def tearDownDropEntitiesSqlAlchemy(test):
_execute_statements(ddl_statements)


class HttpsTestServerLayer:
PORT = 65534
HOST = "localhost"
CERT_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__),
"pki/server_valid.pem"))
CACERT_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__),
"pki/cacert_valid.pem"))

__name__ = "httpsserver"
__bases__ = tuple()

class HttpsServer(HTTPServer):
def get_request(self):

# Prepare SSL context.
context = ssl._create_unverified_context(
protocol=ssl.PROTOCOL_TLS_SERVER,
cert_reqs=ssl.CERT_OPTIONAL,
check_hostname=False,
purpose=ssl.Purpose.CLIENT_AUTH,
certfile=HttpsTestServerLayer.CERT_FILE,
keyfile=HttpsTestServerLayer.CERT_FILE,
cafile=HttpsTestServerLayer.CACERT_FILE)

# Set minimum protocol version, TLSv1 and TLSv1.1 are unsafe.
context.minimum_version = ssl.TLSVersion.TLSv1_2

# Wrap TLS encryption around socket.
socket, client_address = HTTPServer.get_request(self)
socket = context.wrap_socket(socket, server_side=True)

return socket, client_address

class HttpsHandler(BaseHTTPRequestHandler):

payload = json.dumps({"name": "test", "status": 200, })

def do_GET(self):
self.send_response(200)
payload = self.payload.encode('UTF-8')
self.send_header("Content-Length", len(payload))
self.send_header("Content-Type", "application/json; charset=UTF-8")
self.end_headers()
self.wfile.write(payload)

def setUp(self):
self.server = self.HttpsServer(
(self.HOST, self.PORT),
self.HttpsHandler
)
thread = threading.Thread(target=self.serve_forever)
thread.daemon = True # quit interpreter when only thread exists
thread.start()
self.waitForServer()

def serve_forever(self):
print("listening on", self.HOST, self.PORT)
self.server.serve_forever()
print("server stopped.")

def tearDown(self):
self.server.shutdown()
self.server.server_close()

def isUp(self):
"""
Test if a host is up.
"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ex = s.connect_ex((self.HOST, self.PORT))
s.close()
return ex == 0

def waitForServer(self, timeout=5):
"""
Wait for the host to be available.
"""
with stopit.ThreadingTimeout(timeout) as to_ctx_mgr:
while True:
if self.isUp():
break
time.sleep(0.001)

if not to_ctx_mgr:
raise TimeoutError("Could not properly start embedded webserver "
"within {} seconds".format(timeout))


def setUpWithHttps(test):
test.globs['crate_host'] = "https://{0}:{1}".format(
HttpsTestServerLayer.HOST, HttpsTestServerLayer.PORT
)
test.globs['pprint'] = pprint
test.globs['print'] = cprint

test.globs['cacert_valid'] = os.path.abspath(
os.path.join(os.path.dirname(__file__), "pki/cacert_valid.pem")
)
test.globs['cacert_invalid'] = os.path.abspath(
os.path.join(os.path.dirname(__file__), "pki/cacert_invalid.pem")
)
test.globs['clientcert_valid'] = os.path.abspath(
os.path.join(os.path.dirname(__file__), "pki/client_valid.pem")
)
test.globs['clientcert_invalid'] = os.path.abspath(
os.path.join(os.path.dirname(__file__), "pki/client_invalid.pem")
)


def _execute_statements(statements, on_error="ignore"):
with connect(crate_host) as conn:
cursor = conn.cursor()
Expand All @@ -337,9 +163,6 @@ def test_suite():
suite = unittest.TestSuite()
flags = (doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS)

# Unit tests.
suite.addTest(sqlalchemy_test_suite_unit())

sqlalchemy_integration_tests = [
'docs/by-example/sqlalchemy/getting-started.rst',
'docs/by-example/sqlalchemy/crud.rst',
Expand All @@ -363,8 +186,6 @@ def test_suite():
optionflags=flags,
encoding='utf-8'
)
s.layer = ensure_cratedb_layer()
s.addTest(sqlalchemy_test_suite_integration())
suite.addTest(s)

return suite
Empty file removed src/crate/testing/__init__.py
Empty file.
34 changes: 0 additions & 34 deletions src/crate/testing/tests.py

This file was deleted.

57 changes: 56 additions & 1 deletion src/sqlalchemy_cratedb/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,56 @@
DUMMY = 42
# -*- coding: utf-8; -*-
#
# Licensed to CRATE Technology GmbH ("Crate") under one or more contributor
# license agreements. See the NOTICE file distributed with this work for
# additional information regarding copyright ownership. Crate licenses
# this file to you under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. You may
# obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# However, if you have executed another commercial license agreement
# with Crate these terms will supersede the license and you may use the
# software solely pursuant to the terms of the relevant commercial agreement.

from .compat.api13 import monkeypatch_add_exec_driver_sql
from .dialect import CrateDialect
from .sa_version import SA_1_4, SA_2_0, SA_VERSION
from .support import insert_bulk
from .types import Geopoint, Geoshape, ObjectArray, ObjectType


if SA_VERSION < SA_1_4:
import textwrap
import warnings

# SQLAlchemy 1.3 is effectively EOL.
SA13_DEPRECATION_WARNING = textwrap.dedent("""
WARNING: SQLAlchemy 1.3 is effectively EOL.
SQLAlchemy 1.3 is EOL since 2023-01-27.
Future versions of the CrateDB SQLAlchemy dialect will drop support for SQLAlchemy 1.3.
It is recommended that you transition to using SQLAlchemy 1.4 or 2.0:
- https://docs.sqlalchemy.org/en/14/changelog/migration_14.html
- https://docs.sqlalchemy.org/en/20/changelog/migration_20.html
""".lstrip("\n"))
warnings.warn(message=SA13_DEPRECATION_WARNING, category=DeprecationWarning)

# SQLAlchemy 1.3 does not have the `exec_driver_sql` method, so add it.
monkeypatch_add_exec_driver_sql()


__all__ = [
CrateDialect,
Geopoint,
Geoshape,
ObjectArray,
ObjectType,
]
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 644a12f

Please sign in to comment.