Skip to content

Commit

Permalink
Client.from_url method
Browse files Browse the repository at this point in the history
  • Loading branch information
xzkostyan committed Sep 14, 2019
1 parent b412fd4 commit 41f3950
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 1 deletion.
89 changes: 89 additions & 0 deletions clickhouse_driver/client.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ssl
from time import time
import types

Expand All @@ -8,6 +9,7 @@
from .result import (
IterQueryResult, ProgressQueryResult, QueryResult, QueryInfo
)
from .util.compat import urlparse, parse_qs, asbool
from .util.escape import escape_params
from .util.helpers import chunks

Expand Down Expand Up @@ -394,3 +396,90 @@ def substitute_params(self, query, params):

escaped = escape_params(params)
return query % escaped

@classmethod
def from_url(cls, url):
"""
Return a client configured from the given URL.
For example::
clickhouse://[user:password]@localhost:9000/default
clickhouses://[user:password]@localhost:9440/default
Three URL schemes are supported:
clickhouse:// creates a normal TCP socket connection
clickhouses:// creates a SSL wrapped TCP socket connection
Any additional querystring arguments will be passed along to
the Connection class's initializer.
"""
url = urlparse(url)

settings = {}
kwargs = {}

host = url.hostname

if url.port is not None:
kwargs['port'] = url.port

path = url.path.replace('/', '', 1)
if path:
kwargs['database'] = path

if url.username is not None:
kwargs['user'] = url.username

if url.password is not None:
kwargs['password'] = url.password

if url.scheme == 'clickhouses':
kwargs['secure'] = True

compression_algs = {'lz4', 'lz4hc', 'zstd'}
timeouts = {
'connect_timeout',
'send_receive_timeout',
'sync_request_timeout'
}

for name, value in parse_qs(url.query).items():
if not value or not len(value):
continue

value = value[0]

if name == 'compression':
value = value.lower()
if value in compression_algs:
kwargs[name] = value
else:
kwargs[name] = asbool(value)

elif name == 'secure':
kwargs[name] = asbool(value)

elif name == 'client_name':
kwargs[name] = value

elif name in timeouts:
kwargs[name] = float(value)

elif name == 'compress_block_size':
kwargs[name] = int(value)

# ssl
if name == 'verify':
kwargs[name] = asbool(value)
elif name == 'ssl_version':
kwargs[name] = getattr(ssl, value)
elif name in ['ca_certs', 'ciphers']:
kwargs[name] = value

settings[name] = value

if settings:
kwargs['settings'] = settings

return cls(host, **kwargs)
2 changes: 1 addition & 1 deletion clickhouse_driver/settings/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def write(cls, value, buf):
class SettingUInt64(SettingType):
@classmethod
def write(cls, value, buf):
write_varint(value, buf)
write_varint(int(value), buf)


class SettingBool(SettingType):
Expand Down
17 changes: 17 additions & 0 deletions clickhouse_driver/util/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@


if PY3:
from urllib.parse import parse_qs, urlparse # noqa: F401

string_types = str,
integer_types = int,
class_types = type,
Expand All @@ -15,9 +17,24 @@
range = range

else:
from urlparse import parse_qs, urlparse # noqa: F401

string_types = basestring, # noqa: F821
integer_types = (int, long) # noqa: F821
class_types = (type, types.ClassType)
text_type = unicode # noqa: F821
binary_type = str
range = xrange # noqa: F821


# from paste.deploy.converters
def asbool(obj):
if isinstance(obj, string_types):
obj = obj.strip().lower()
if obj in ['true', 'yes', 'on', 'y', 't', '1']:
return True
elif obj in ['false', 'no', 'off', 'n', 'f', '0']:
return False
else:
raise ValueError('String is not true/false: %r' % obj)
return bool(obj)
155 changes: 155 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import ssl
from unittest import TestCase

from clickhouse_driver import Client
from clickhouse_driver.compression.lz4 import Compressor as LZ4Compressor
from clickhouse_driver.compression.lz4hc import Compressor as LZHC4Compressor
from clickhouse_driver.compression.zstd import Compressor as ZSTDCompressor
from clickhouse_driver.protocol import Compression


class ClientFromUrlTestCase(TestCase):

def test_simple(self):
c = Client.from_url('clickhouse://host')

assert c.connection.host == 'host'
assert c.connection.database == 'default'

c = Client.from_url('clickhouse://host/db')

assert c.connection.host == 'host'
assert c.connection.database == 'db'

def test_credentials(self):
c = Client.from_url('clickhouse://host/db')

assert c.connection.user == 'default'
assert c.connection.password == ''

c = Client.from_url('clickhouse://admin:secure@host/db')

assert c.connection.user == 'admin'
assert c.connection.password == 'secure'

c = Client.from_url('clickhouse://user:@host/db')

assert c.connection.user == 'user'
assert c.connection.password == ''

def test_schema(self):
c = Client.from_url('clickhouse://host')
assert c.connection.secure_socket is False

c = Client.from_url('clickhouses://host')
assert c.connection.secure_socket is True

c = Client.from_url('test://host')
assert c.connection.secure_socket is False

def test_port(self):
c = Client.from_url('clickhouse://host')
assert c.connection.port == 9000

c = Client.from_url('clickhouses://host')
assert c.connection.port == 9440

c = Client.from_url('clickhouses://host:1234')
assert c.connection.port == 1234

def test_secure(self):
c = Client.from_url('clickhouse://host?secure=n')
assert c.connection.port == 9000
assert c.connection.secure_socket is False

c = Client.from_url('clickhouse://host?secure=y')
assert c.connection.port == 9440
assert c.connection.secure_socket is True

c = Client.from_url('clickhouse://host:1234?secure=y')
assert c.connection.port == 1234
assert c.connection.secure_socket is True

with self.assertRaises(ValueError):
Client.from_url('clickhouse://host:1234?secure=nonono')

def test_compression(self):
c = Client.from_url('clickhouse://host?compression=n')
assert c.connection.compression == Compression.DISABLED
assert c.connection.compressor_cls is None

c = Client.from_url('clickhouse://host?compression=y')
assert c.connection.compression == Compression.ENABLED
assert c.connection.compressor_cls is LZ4Compressor

c = Client.from_url('clickhouse://host?compression=lz4')
assert c.connection.compression == Compression.ENABLED
assert c.connection.compressor_cls is LZ4Compressor

c = Client.from_url('clickhouse://host?compression=lz4hc')
assert c.connection.compression == Compression.ENABLED
assert c.connection.compressor_cls is LZHC4Compressor

c = Client.from_url('clickhouse://host?compression=zstd')
assert c.connection.compression == Compression.ENABLED
assert c.connection.compressor_cls is ZSTDCompressor

with self.assertRaises(ValueError):
Client.from_url('clickhouse://host:1234?compression=custom')

def test_client_name(self):
c = Client.from_url('clickhouse://host?client_name=native')
assert c.connection.client_name == 'ClickHouse native'

def test_timeouts(self):
with self.assertRaises(ValueError):
Client.from_url('clickhouse://host?connect_timeout=test')

c = Client.from_url('clickhouse://host?connect_timeout=1.2')
assert c.connection.connect_timeout == 1.2

c = Client.from_url('clickhouse://host?send_receive_timeout=1.2')
assert c.connection.send_receive_timeout == 1.2

c = Client.from_url('clickhouse://host?sync_request_timeout=1.2')
assert c.connection.sync_request_timeout == 1.2

def test_compress_block_size(self):
with self.assertRaises(ValueError):
Client.from_url('clickhouse://host?compress_block_size=test')

c = Client.from_url('clickhouse://host?compress_block_size=100500')
# compression is not set
assert c.connection.compress_block_size is None

c = Client.from_url(
'clickhouse://host?'
'compress_block_size=100500&'
'compression=1'
)
assert c.connection.compress_block_size == 100500

def test_settings(self):
c = Client.from_url(
'clickhouse://host?'
'send_logs_level=trace&'
'max_block_size=123'
)
assert c.settings == {
'send_logs_level': 'trace',
'max_block_size': '123'
}

def test_ssl(self):
c = Client.from_url(
'clickhouses://host?'
'verify=false&'
'ssl_version=PROTOCOL_SSLv23&'
'ca_certs=/tmp/certs&'
'ciphers=HIGH:-aNULL:-eNULL:-PSK:RC4-SHA:RC4-MD5'
)
assert c.connection.ssl_options == {
'ssl_version': ssl.PROTOCOL_SSLv23,
'ca_certs': '/tmp/certs',
'ciphers': 'HIGH:-aNULL:-eNULL:-PSK:RC4-SHA:RC4-MD5'
}

0 comments on commit 41f3950

Please sign in to comment.