diff --git a/doublethink/__init__.py b/doublethink/__init__.py index 057d8da..cfb220c 100644 --- a/doublethink/__init__.py +++ b/doublethink/__init__.py @@ -19,12 +19,19 @@ limitations under the License. import rethinkdb import datetime +try: + import urllib.parse as urllib_parse +except: + import urlparse as urllib_parse +import collections from doublethink.orm import Document from doublethink.rethinker import Rethinker from doublethink.services import ServiceRegistry -__all__ = ['Document', 'Rethinker', 'ServiceRegistry', 'UTC', 'utcnow'] +__all__ = [ + 'Document', 'Rethinker', 'ServiceRegistry', 'UTC', 'utcnow', + 'parse_rethinkdb_url', 'ParsedRethinkDbUrl'] try: UTC = datetime.timezone.utc @@ -38,3 +45,46 @@ def utcnow(): 2 doesn't come with a timezone implementation.""" return datetime.datetime.now(UTC) +ParsedRethinkDbUrl = collections.namedtuple( + 'ParsedRethinkDbUrl', ['hosts', 'database', 'table']) + +def parse_rethinkdb_url(s): + ''' + Parses a url like this rethinkdb://server1:port,server2:port/database/table + + Returns: + tuple `(['server1:port', 'server2:port'], database, table)` + `table` and `database` may be None + + Raises: + ValueError if url cannot be pasrsed a a rethinkdb url + + There is some precedent for this kind of url (though only with a single + host): + - https://gist.github.com/lucidfrontier45/e5881a8fca25e51ab21c3cf4b4179daa + - https://github.com/laggyluke/node-parse-rethinkdb-url + ''' + result = ParsedRethinkDbUrl(None, None, None) + parsed = urllib_parse.urlparse(s) + if parsed.scheme != 'rethinkdb': + raise ValueError + hosts = parsed.netloc.split(',') + + database = None + table = None + path_segments = parsed.path.split('/')[1:] + if len(path_segments) >= 3: + raise ValueError + if len(path_segments) >= 1: + database = path_segments[0] + if len(path_segments) == 2: + table = path_segments[1] + + if '' in hosts or database == '' or table == '': + raise ValueError + + if any('@' in host for host in hosts): + raise ValueError + + return ParsedRethinkDbUrl(hosts, database, table) + diff --git a/setup.py b/setup.py index 7bc3965..4c80ddc 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import codecs setuptools.setup( name='doublethink', - version='0.2.0.dev85', + version='0.2.0.dev86', packages=['doublethink'], classifiers=[ 'Programming Language :: Python :: 2.7', diff --git a/tests/test_misc.py b/tests/test_misc.py new file mode 100644 index 0000000..4502bd4 --- /dev/null +++ b/tests/test_misc.py @@ -0,0 +1,83 @@ +''' +tests_misc.py - tests for doublethink miscellany + +Copyright (C) 2017 Internet Archive + +Licensed 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. +''' + +import doublethink +import logging +import sys +import pytest +import rethinkdb as r +from doublethink import parse_rethinkdb_url + +logging.basicConfig( + stream=sys.stderr, level=logging.INFO, format=( + '%(asctime)s %(process)d %(levelname)s %(threadName)s ' + '%(name)s.%(funcName)s(%(filename)s:%(lineno)d) %(message)s')) + +def test_parse_rethinkdb_url(): + assert parse_rethinkdb_url('rethinkdb://foo/bar/baz') == (['foo'], 'bar', 'baz') + assert parse_rethinkdb_url('rethinkdb://foo/bar') == (['foo'], 'bar', None) + assert parse_rethinkdb_url('rethinkdb://foo') == (['foo'], None, None) + assert parse_rethinkdb_url('rethinkdb://foo,goo/bar/baz') == (['foo', 'goo'], 'bar', 'baz') + assert parse_rethinkdb_url('rethinkdb://foo,goo/bar') == (['foo', 'goo'], 'bar', None) + assert parse_rethinkdb_url('rethinkdb://foo,goo') == (['foo', 'goo'], None, None) + assert parse_rethinkdb_url('rethinkdb://foo,goo:38015/bar/baz') == (['foo', 'goo:38015'], 'bar', 'baz') + assert parse_rethinkdb_url('rethinkdb://foo,goo:38015/bar') == (['foo', 'goo:38015'], 'bar', None) + assert parse_rethinkdb_url('rethinkdb://foo,goo:38015') == (['foo', 'goo:38015'], None, None) + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb://foo,goo/') + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb://foo,goo:38015/') + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb://foo,goo:38015/bar/') + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb://foo,goo:38015/bar/baz/') + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb://foo,goo/bar/baz/') + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb://foo/bar/baz/') + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb://foo/bar/') + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb://foo/') + with pytest.raises(ValueError): + parse_rethinkdb_url('http://foo/bar/baz') + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb://foo,goo/bar/') + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb://') + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb:///a') + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb:///a/b') + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb://a,/') + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb://,b/') + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb://foo/bar/baz/quux') + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb://foo/bar/baz/quux') + # we don't support rethinkdb auth + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb://u@foo/bar/baz') + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb://u:p@foo/bar/baz') + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb://foo,u@goo/bar/baz') + with pytest.raises(ValueError): + parse_rethinkdb_url('rethinkdb://foo,u:p@goo/bar/baz')